<!-- build time:Tue Feb 18 2020 17:19:28 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot 安全框架 Spring Security 入门 | calm&#39;s blog</title><meta name="description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Spring-Security&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述FROM 《认证 (authentication) 和授权 (authorization) 的区别》authentication [ɔ,θɛntɪ’keʃən] 认证authorization [,ɔθər"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 安全框架 Spring Security 入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Spring-Security&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述FROM 《认证 (authentication) 和授权 (authorization) 的区别》authentication [ɔ,θɛntɪ’keʃən] 认证authorization [,ɔθər"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/1.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/2.png"><meta property="article:published_time" content="2019-12-29T14:37:47.000Z"><meta property="article:modified_time" content="2020-01-07T06:21:07.395Z"><meta property="article:author" content="calm"><meta property="article:tag" content="Spring Security"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/1.png"><link rel="canonical" href="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-快速入门"><span class="toc-text">2. 快速入门</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-引入依赖"><span class="toc-text">2.1. 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-Application"><span class="toc-text">2.2. Application</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-配置文件"><span class="toc-text">2.3. 配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-AdminController"><span class="toc-text">2.4. AdminController</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-5-简单测试"><span class="toc-text">2.5. 简单测试</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-进阶使用"><span class="toc-text">3. 进阶使用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-引入依赖"><span class="toc-text">3.1. 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-示例一"><span class="toc-text">3.2. 示例一</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-1-SecurityConfig"><span class="toc-text">3.2.1. SecurityConfig</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-2-TestController"><span class="toc-text">3.2.2. TestController</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-示例二"><span class="toc-text">3.3. 示例二</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3-1-SecurityConfig"><span class="toc-text">3.3.1. SecurityConfig</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3-2-DemoController"><span class="toc-text">3.3.2. DemoController</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4-整合-Spring-Session"><span class="toc-text">4. 整合 Spring Session</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5-整合-OAuth2"><span class="toc-text">5. 整合 OAuth2</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#6-整合-JWT"><span class="toc-text">6. 整合 JWT</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#7-项目实战"><span class="toc-text">7. 项目实战</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#7-1-表结构"><span class="toc-text">7.1. 表结构</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-1-SysUser"><span class="toc-text">7.1.1. SysUser</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-2-SysRole"><span class="toc-text">7.1.2. SysRole</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-3-SysUserRole"><span class="toc-text">7.1.3. SysUserRole</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-4-SysMenu"><span class="toc-text">7.1.4. SysMenu</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-5-SysRoleMenu"><span class="toc-text">7.1.5. SysRoleMenu</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-2-SecurityConfig"><span class="toc-text">7.2. SecurityConfig</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-3-登陆-API-接口"><span class="toc-text">7.3. 登陆 API 接口</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-3-1-加载用户信息"><span class="toc-text">7.3.1. 加载用户信息</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-3-2-创建认证-Token"><span class="toc-text">7.3.2. 创建认证 Token</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-4-JwtAuthenticationTokenFilter"><span class="toc-text">7.4. JwtAuthenticationTokenFilter</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-5-权限验证"><span class="toc-text">7.5. 权限验证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-5-1-判断是否有权限"><span class="toc-text">7.5.1. 判断是否有权限</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-5-2-判断是否有角色"><span class="toc-text">7.5.2. 判断是否有角色</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-6-各种处理器"><span class="toc-text">7.6. 各种处理器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-6-1-AuthenticationEntryPointImpl"><span class="toc-text">7.6.1. AuthenticationEntryPointImpl</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-6-2-GlobalExceptionHandler"><span class="toc-text">7.6.2. GlobalExceptionHandler</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-6-3-LogoutSuccessHandlerImpl"><span class="toc-text">7.6.3. LogoutSuccessHandlerImpl</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-7-登陆日志"><span class="toc-text">7.7. 登陆日志</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-8-获得用户信息-API-接口"><span class="toc-text">7.8. 获得用户信息 API 接口</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-9-获取路由信息"><span class="toc-text">7.9. 获取路由信息</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-10-权限管理"><span class="toc-text">7.10. 权限管理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7-11-小小的建议"><span class="toc-text">7.11. 小小的建议</span></a></li></ol></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-security" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot 安全框架 Spring Security 入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2019/12/29/labs-springboot2-security/" class="article-date"><time datetime="2019-12-29T14:37:47.000Z" itemprop="datePublished">2019-12-29</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2019/12/29/labs-springboot2-security/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 12.1k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 53(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><blockquote><p>摘要: 原创出处 <a href="http://www.iocoder.cn/Spring-Boot/Spring-Security/" target="_blank" rel="noopener">http://www.iocoder.cn/Spring-Boot/Spring-Security/</a> 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！</p></blockquote><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><blockquote><p>FROM <a href="https://www.cnblogs.com/joooy/archive/2010/08/08/1795257.html" target="_blank" rel="noopener">《认证 (authentication) 和授权 (authorization) 的区别》</a></p><ul><li>authentication [ɔ,θɛntɪ’keʃən] 认证</li><li>authorization [,ɔθərɪ’zeʃən] 授权</li></ul><p>以<strong>打飞机</strong>举例子：</p><ul><li>【认证】你要登机，你需要出示你的 passport 和 ticket，passport 是为了证明你张三确实是你张三，这就是 authentication。</li><li>【授权】而机票是为了证明你张三确实买了票可以上飞机，这就是 authorization。</li></ul><p>以<strong>论坛</strong>举例子：</p><ul><li>【认证】你要登陆论坛，输入用户名张三，密码 1234，密码正确，证明你张三确实是张三，这就是 authentication。</li><li>【授权】再一 check 用户张三是个版主，所以有权限加精删别人帖，这就是 authorization 。</li></ul></blockquote><p>所以简单来说：认证解决“你是谁”的问题，授权解决“你能做什么”的问题。另外，在推荐阅读下<a href="http://www.iocoder.cn/Fight/user_login_auth_terms/?self" target="_blank" rel="noopener">《认证、授权、鉴权和权限控制》</a> 文章，更加<strong>详细明确</strong>。</p><p>Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架，用于保护基于 Spring 的应用程序。与所有 Spring 项目一样，它很容易扩展以满足定制需求。</p><h1 id="2-快速入门"><a href="#2-快速入门" class="headerlink" title="2. 快速入门"></a><span id="anchor-2">2. 快速入门</span></h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-security-control/labs-springboot2-security/labs-springboot2-security-demo" target="_blank" rel="noopener">labs-springboot2-security-demo</a>。</p></blockquote><p>在本小节中，我们来快速入门下 Spring Security ，实现访问 API 接口时，需要首先进行登陆，才能进行访问。</p><h2 id="2-1-引入依赖"><a href="#2-1-引入依赖" class="headerlink" title="2.1. 引入依赖"></a><span id="anchor-2.1">2.1. 引入依赖</span></h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.1.10.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">relativePath</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-security-demo<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring Security 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="2-2-Application"><a href="#2-2-Application" class="headerlink" title="2.2. Application"></a>2.2. Application</h2><p>创建 <code>Application</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Application.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-3-配置文件"><a href="#2-3-配置文件" class="headerlink" title="2.3. 配置文件"></a><span id="anchor-2.3">2.3. 配置文件</span></h2><p>在 <code>application.yml</code> 中，添加 Spring Security 配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="comment"># Spring Security 配置项，对应 SecurityProperties 配置类</span></span><br><span class="line">  <span class="attr">security:</span></span><br><span class="line">    <span class="comment"># 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。</span></span><br><span class="line">    <span class="attr">user:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">user</span> <span class="comment"># 账号</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">user</span> <span class="comment"># 密码</span></span><br><span class="line">      <span class="attr">roles:</span> <span class="string">ADMIN</span> <span class="comment"># 拥有角色</span></span><br></pre></td></tr></table></figure><ul><li>在 <code>spring.security</code> 配置项，设置 Spring Security 的配置，对应 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java" target="_blank" rel="noopener">SecurityProperties</a> 配置类。</li><li>默认情况下，Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java" target="_blank" rel="noopener">UserDetailsServiceAutoConfiguration</a> 自动化配置类，会创建一个<strong>内存级别</strong>的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java" target="_blank" rel="noopener">InMemoryUserDetailsManager</a> Bean 对象，提供认证的用户信息。<ul><li>这里，我们<strong>添加了</strong> <code>spring.security.user</code> 配置项，<code>UserDetailsServiceAutoConfiguration</code> 会基于配置的信息创建一个用户 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/User.java" target="_blank" rel="noopener">User</a> 在内存中。</li><li>如果，我们<strong>未添加</strong> <code>spring.security.user</code> 配置项，<code>UserDetailsServiceAutoConfiguration</code> 会自动创建一个用户名为 <code>user</code> ，密码为 UUID 随机的用户 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/User.java" target="_blank" rel="noopener">User</a> 在内存中。</li></ul></li></ul><h2 id="2-4-AdminController"><a href="#2-4-AdminController" class="headerlink" title="2.4. AdminController"></a>2.4. AdminController</h2><p>创建 <code>AdminController</code> 类，提供管理员 API 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AdminController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/admin"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AdminController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/demo"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">demo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"示例返回"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们先提供一个 <code>/admin/demo</code> 接口，用于测试未登陆时，会被拦截到登陆界面。</li></ul><h2 id="2-5-简单测试"><a href="#2-5-简单测试" class="headerlink" title="2.5. 简单测试"></a>2.5. 简单测试</h2><p>执行 <code>Application#main(String[] args)</code> 方法，运行项目。</p><p>项目启动成功后，浏览器访问 <a href="http://127.0.0.1:8080/admin/demo" target="_blank" rel="noopener">http://127.0.0.1:8080/admin/demo</a> 接口。因为未登陆，所以被 Spring Security 拦截到登陆界面。如下图所示：<img src="1.png" alt="默认登陆界面"></p><p>因为我们没有<strong>自定义</strong>登陆界面，所以默认会使用 <a href="https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java" target="_blank" rel="noopener">DefaultLoginPageGeneratingFilter</a> 类，生成上述界面。</p><p>输入我们在<a href="#anchor-2.3">「2.3. 配置文件」</a>中配置的「user/user」账号，进行登陆。登陆完成后，因为 Spring Security 会记录被拦截的访问地址，所以浏览器自动动跳转 <a href="http://127.0.0.1:8080/admin/demo" target="_blank" rel="noopener">http://127.0.0.1:8080/admin/demo</a> 接口。访问结果如下图所示：<img src="2.png" alt=" 接口的结果"></p><h1 id="3-进阶使用"><a href="#3-进阶使用" class="headerlink" title="3. 进阶使用"></a><span id="anchor-3">3. 进阶使用</span></h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-security-control/labs-springboot2-security/labs-springboot2-security-demo-role" target="_blank" rel="noopener">labs-springboot2-security-demo-role</a> 。</p></blockquote><p>在<a href="#anchor-2">「 2. 快速入门」</a>中，我们很<strong>快速</strong>的完成了 Spring Security 的入门。本小节，我们将会自定义 Spring Security 的配置，实现<strong>权限控制</strong>。</p><p>考虑到不污染上述的示例，我们新建一个 <a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-security-control/labs-springboot2-security/labs-springboot2-security-demo-role" target="_blank" rel="noopener">labs-springboot2-security-demo-role</a> 项目。</p><h2 id="3-1-引入依赖"><a href="#3-1-引入依赖" class="headerlink" title="3.1. 引入依赖"></a>3.1. 引入依赖</h2><p>和 <a href="#anchor-2.1">「2.1. 引入依赖」</a> 一致，见 <code>pom.xml</code> 文件。</p><h2 id="3-2-示例一"><a href="#3-2-示例一" class="headerlink" title="3.2. 示例一"></a>3.2. 示例一</h2><p>在<strong>示例一</strong>中，我们会看看如何自定义 Spring Security 的配置，实现<strong>权限控制</strong>。</p><h3 id="3-2-1-SecurityConfig"><a href="#3-2-1-SecurityConfig" class="headerlink" title="3.2.1. SecurityConfig"></a><span id="anchor-3.2.1">3.2.1. SecurityConfig</span></h3><p>创建 <code>SecurityConfig</code>配置类，继承 <a href="https://github.com/spring-projects/spring-security/blob/master/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java" target="_blank" rel="noopener">WebSecurityConfigurerAdapter</a> 抽象类，实现 Spring Security 在 Web 场景下的自定义配置。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>我们可以通过重写 WebSecurityConfigurerAdapter 的方法，实现自定义的 Spring Security 的配置。</li></ul><p>首先，我们重写 <code>#configure(AuthenticationManagerBuilder auth)</code> 方法，实现 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/authentication/AuthenticationManager.java" target="_blank" rel="noopener">AuthenticationManager</a> 认证管理器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthenticationManagerBuilder auth)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    auth</span><br><span class="line">        <span class="comment">// &lt;X&gt; 使用内存中的 InMemoryUserDetailsManager</span></span><br><span class="line">        .inMemoryAuthentication()</span><br><span class="line">        <span class="comment">// &lt;Y&gt; 不使用 PasswordEncoder 密码编码器</span></span><br><span class="line">        .passwordEncoder(NoOpPasswordEncoder.getInstance())</span><br><span class="line">        <span class="comment">// &lt;Z&gt; 配置 admin 用户</span></span><br><span class="line">        .withUser(<span class="string">"admin"</span>).password(<span class="string">"admin"</span>).roles(<span class="string">"ADMIN"</span>)</span><br><span class="line">        <span class="comment">// &lt;Z&gt; 配置 normal 用户</span></span><br><span class="line">        .and().withUser(<span class="string">"normal"</span>).password(<span class="string">"normal"</span>).roles(<span class="string">"NORMAL"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;X&gt;</code> 处，调用 <code>AuthenticationManagerBuilder#inMemoryAuthentication()</code>方法，使用<strong>内存级别</strong>的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java" target="_blank" rel="noopener">InMemoryUserDetailsManager</a> Bean 对象，提供认证的用户信息。<ul><li>Spring 内置了两种 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/provisioning/UserDetailsManager.java" target="_blank" rel="noopener">UserDetailsManager</a> 实现：<ul><li>InMemoryUserDetailsManager，和<a href="#anchor-2">「2. 快速入门」</a>是一样的。</li><li>JdbcUserDetailsManager ，基于 <strong>JDBC</strong>的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java" target="_blank" rel="noopener">JdbcUserDetailsManager</a> 。</li></ul></li><li>实际项目中，我们更多采用调用 <code>AuthenticationManagerBuilder#userDetailsService(userDetailsService)</code> 方法，使用自定义实现的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetailsService.java" target="_blank" rel="noopener">UserDetailsService</a> 实现类，更加<strong>灵活</strong>且<strong>自由</strong>的实现认证的用户信息的读取。</li></ul></li><li><code>&lt;Y&gt;</code> 处，调用 <code>AbstractDaoAuthenticationConfigurer#passwordEncoder(passwordEncoder)</code>方法，设置 PasswordEncoder 密码编码器。<ul><li>在这里，为了方便，我们使用 <a href="https://github.com/spring-projects/spring-security/blob/master/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java" target="_blank" rel="noopener">NoOpPasswordEncoder</a> 。实际上，等于不使用 PasswordEncoder ，不配置的话会报错。</li><li>生产环境下，推荐使用 <a href="https://github.com/spring-projects/spring-security/blob/master/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java" target="_blank" rel="noopener">BCryptPasswordEncoder</a> 。更多关于 PasswordEncoder 的内容，推荐阅读<a href="http://www.iocoder.cn/Spring-Security/laoxu/PasswordEncoder/?self" target="_blank" rel="noopener">《该如何设计你的 PasswordEncoder?》</a>文章。</li></ul></li><li><code>&lt;Z&gt;</code> 处，配置了 <code>「admin/admin」</code>和 <code>「normal/normal」</code> 两个用户，分别对应 ADMIN 和 NORMAL 角色。相比 <a href="#anchor-2">「2. 快速入门」</a> 来说，可以配置更多的用户。</li></ul><p>然后，我们重写 <code>#configure(HttpSecurity http)</code> 方法，主要配置 URL 的权限控制。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line">	<span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">		http</span><br><span class="line">			<span class="comment">// &lt;X&gt; 配置请求地址的权限</span></span><br><span class="line">			.authorizeRequests()</span><br><span class="line">          <span class="comment">// 所有用户可访问</span></span><br><span class="line">          .antMatchers(<span class="string">"/test/echo"</span>).permitAll()</span><br><span class="line">          <span class="comment">// 需要 ADMIN 角色</span></span><br><span class="line">          .antMatchers(<span class="string">"/test/admin"</span>).hasRole(<span class="string">"ADMIN"</span>)</span><br><span class="line">          <span class="comment">// 需要 NORMAL 角色。</span></span><br><span class="line">          .antMatchers(<span class="string">"/test/normal"</span>).access(<span class="string">"hasRole('ROLE_NORMAL')"</span>)</span><br><span class="line">          <span class="comment">// 任何请求，访问的用户都需要经过认证</span></span><br><span class="line">          .anyRequest().authenticated()</span><br><span class="line">			.and()</span><br><span class="line">			<span class="comment">// &lt;Y&gt; 设置 Form 表单登陆</span></span><br><span class="line">			.formLogin()</span><br><span class="line">			<span class="comment">// 登陆 URL 地址</span></span><br><span class="line">			<span class="comment">// .loginPage("/login")</span></span><br><span class="line">			.permitAll() <span class="comment">// 所有用户可访问</span></span><br><span class="line">			.and()</span><br><span class="line">			<span class="comment">// 配置退出相关</span></span><br><span class="line">			.logout()</span><br><span class="line">			<span class="comment">// 退出 URL 地址</span></span><br><span class="line">			<span class="comment">// .logoutUrl("/logout")</span></span><br><span class="line">			<span class="comment">// 所有用户可访问</span></span><br><span class="line">			.permitAll();</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;X&gt;</code> 处，调用 <code>HttpSecurity#authorizeRequests()</code> 方法，开始配置 URL 的<strong>权限控制</strong>。注意看艿艿配置的<strong>四个</strong>权限控制的配置。下面，是配置权限控制会使用到的方法：<ul><li><code>#(String... antPatterns)</code> 方法，配置匹配的 URL 地址，基于 <a href="https://blog.csdn.net/songdexv/article/details/7219686" target="_blank" rel="noopener">Ant 风格路径表达式</a> ，可传入多个。</li><li>【常用】<code>#permitAll()</code> 方法，所有用户可访问。</li><li>【常用】<code>#denyAll()</code> 方法，所有用户不可访问。</li><li>【常用】<code>#authenticated()</code> 方法，登陆用户可访问。</li><li><code>#anonymous()</code> 方法，无需登陆，即匿名用户可访问。</li><li><code>#rememberMe()</code> 方法，通过 <a href="https://docs.spring.io/spring-security/site/docs/3.0.x/reference/remember-me.html" target="_blank" rel="noopener">remember me</a> 登陆的用户可访问。</li><li><code>#fullyAuthenticated()</code> 方法，非 <a href="https://docs.spring.io/spring-security/site/docs/3.0.x/reference/remember-me.html" target="_blank" rel="noopener">remember me</a> 登陆的用户可访问。</li><li><code>#hasIpAddress(String ipaddressExpression)</code> 方法，来自指定 IP 表达式的用户可访问。</li><li>【常用】<code>#hasRole(String role)</code> 方法， 拥有指定角色的用户可访问。</li><li>【常用】<code>#hasAnyRole(String... roles)</code> 方法，拥有指定任一角色的用户可访问。</li><li>【常用】<code>#hasAuthority(String authority)</code> 方法，拥有指定权限(<code>authority</code>)的用户可访问。</li><li>【常用】<code>#hasAuthority(String... authorities)</code> 方法，拥有指定任一权限(<code>authority</code>)的用户可访问。</li><li>【最牛】<code>#access(String attribute)</code> 方法，当 <a href="https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/html/expressions.html" target="_blank" rel="noopener">Spring EL 表达式</a>的执行结果为 <code>true</code> 时，可以访问。</li></ul></li><li><code>&lt;Y&gt;</code> 处，调用 <code>HttpSecurity#formLogin()</code> 方法，设置 Form 表单<strong>登陆</strong>。<ul><li>如果胖友想要自定义登陆页面，可以通过 <code>#loginPage(String loginPage)</code> 方法，来进行设置。不过这里我们希望像 <a href="#anchor-2">「2. 快速入门」</a> 一样，使用默认的登陆界面，所以不进行设置。</li></ul></li><li>`` 处，调用 <code>HttpSecurity#logout()</code> 方法，配置<strong>退出</strong>相关。<ul><li>如果胖友想要自定义退出页面，可以通过 <code>#logoutUrl(String logoutUrl)</code> 方法，来进行设置。不过这里我们希望像 <a href="#anchor-2">「2. 快速入门」</a> 一样，使用默认的退出界面，所以不进行设置。</li></ul></li></ul><h3 id="3-2-2-TestController"><a href="#3-2-2-TestController" class="headerlink" title="3.2.2. TestController"></a><span id="anchor-3.2.2">3.2.2. TestController</span></h3><p>创建 <code>TestController</code> 类，提供测试 API 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TestController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/test"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/echo"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">demo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"示例返回"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/home"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">home</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是首页"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/admin"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">admin</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是管理员"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/normal"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">normal</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是普通用户"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>对于 <code>/test/echo</code> 接口，直接访问，无需登陆。</li><li>对于 <code>/test/home</code> 接口，无法直接访问，需要进行登陆。</li><li>对于 <code>/test/admin</code> 接口，需要登陆 <code>「admin/admin」</code> 用户，因为需要 ADMIN 角色。</li><li>对于 <code>/test/normal</code> 接口，需要登陆 <code>「normal/normal」</code> 用户，因为需要 NORMAL 角色。</li></ul><p>胖友可以按照如上的说明，进行各种测试。例如说，登陆 <code>「normal/normal」</code> 用户后，去访问 <code>/test/admin</code> 接口，会返回 403 界面，无权限~</p><h2 id="3-3-示例二"><a href="#3-3-示例二" class="headerlink" title="3.3. 示例二"></a>3.3. 示例二</h2><p>在<strong>示例二</strong>中，我们会看看如何使用 Spring Security 的注解，实现权限控制。</p><h3 id="3-3-1-SecurityConfig"><a href="#3-3-1-SecurityConfig" class="headerlink" title="3.3.1. SecurityConfig"></a>3.3.1. SecurityConfig</h3><p>修改 <a href="#anchor-3.2.1">3.2.1 SecurityConfig</a> 配置类，增加 <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html" target="_blank" rel="noopener">@EnableGlobalMethodSecurity</a> 注解，开启对 Spring Security 注解的方法，进行权限验证。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableGlobalMethodSecurity</span>(prePostEnabled = <span class="keyword">true</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span></span></span><br></pre></td></tr></table></figure><h3 id="3-3-2-DemoController"><a href="#3-3-2-DemoController" class="headerlink" title="3.3.2. DemoController"></a>3.3.2. DemoController</h3><p>创建 <code>DemoController</code>类，提供测试 API 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/demo"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PermitAll</span></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/echo"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">demo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"示例返回"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/home"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">home</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是首页"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PreAuthorize</span>(<span class="string">"hasRole('ROLE_ADMIN')"</span>)</span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/admin"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">admin</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是管理员"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PreAuthorize</span>(<span class="string">"hasRole('ROLE_NORMAL')"</span>)</span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/normal"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">normal</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"我是普通用户"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>每个 URL 的权限验证，和<a href="#anchor-3.2.2">「3.2.2 TestController」</a>是一一对应的。</p></li><li><blockquote><p>重要！！！因为在<a href="#anchor-3.2.1">「3.2.1 SecurityConfig」</a>中，配置了 <code>.anyRequest().authenticated()</code> ，任何请求，访问的用户都需要经过认证。所以这里 <code>@PermitAll</code> <strong>注解实际是不生效的</strong>。</p><p>也就是说，Java Config 配置的权限，和注解配置的权限，两者是<strong>叠加</strong>的。</p></blockquote></li><li><p><a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java" target="_blank" rel="noopener"><code>@PreAuthorize</code></a> 注解，等价于 <code>#access(String attribute)</code> 方法，，当 Spring EL 表达式的执行结果为 true 时，可以访问。</p></li></ul><p>Spring Security 还有其它注解，不过不太常用，可见<a href="http://www.iocoder.cn/Fight/Differences-secure-preauthorize-and-rolesallowed/?self" target="_blank" rel="noopener">《区别： @Secured(), @PreAuthorize() 及 @RolesAllowed()》</a>文章。</p><p>胖友可以按照如上的说明，进行各种测试。例如说，登陆 <code>「normal/normal」</code> 用户后，去访问 <code>/test/admin</code> 接口，会返回 403 界面，无权限~</p><h1 id="4-整合-Spring-Session"><a href="#4-整合-Spring-Session" class="headerlink" title="4. 整合 Spring Session"></a>4. 整合 Spring Session</h1><p>TODO 参见<a href="http://www.iocoder.cn/Spring-Boot/Distributed-Session/?self" target="_blank" rel="noopener">《芋道 Spring Boot 分布式 Session 入门》</a>文章的<a href="http://www.iocoder.cn/Spring-Boot/Spring-Security/#" target="_blank" rel="noopener">「5. 整合 Spring Security」</a>小节。</p><h1 id="5-整合-OAuth2"><a href="#5-整合-OAuth2" class="headerlink" title="5. 整合 OAuth2"></a>5. 整合 OAuth2</h1><p>参见<a href="http://www.iocoder.cn/Spring-Security/OAuth2-learning/?self" target="_blank" rel="noopener">《芋道 Spring Security OAuth2 入门》</a>文章，详细到爆炸。</p><h1 id="6-整合-JWT"><a href="#6-整合-JWT" class="headerlink" title="6. 整合 JWT"></a>6. 整合 JWT</h1><p>参见<a href="http://www.iocoder.cn/Fight/Separate-SpringBoot-SpringSecurity-JWT-RBAC-from-front-and-rear-to-achieve-user-stateless-request-authentication/?self" target="_blank" rel="noopener">《前后端分离 SpringBoot + SpringSecurity + JWT + RBAC 实现用户无状态请求验证》</a>文章，写的很不错。</p><h1 id="7-项目实战"><a href="#7-项目实战" class="headerlink" title="7. 项目实战"></a>7. 项目实战</h1><p>在开源项目翻了一圈，找到一个相对合适项目 <a href="https://gitee.com/y_project/RuoYi-Vue" target="_blank" rel="noopener">RuoYi-Vue</a> 。主要以下几点原因：</p><ul><li>基于 Spring Security 实现。</li><li>基于 RBAC 权限模型，并且支持动态的权限配置。</li><li>基于 Redis 服务，实现登陆用户的信息缓存。</li><li>前后端分离。同时前端采用 Vue ，相对来说后端会 Vue 的比 React 的多。</li></ul><p>芋道源码Fork 出一个仓库， 地址是 <a href="https://github.com/YunaiV/RuoYi-Vue" target="_blank" rel="noopener">https://github.com/YunaiV/RuoYi-Vue</a> ，在上面进行注释讲解。卡姆是新建了一个项目，地址是 <a href="https://github.com/yuyong725/calm-ruoyi-security" target="_blank" rel="noopener">https://github.com/yuyong725/calm-ruoyi-security</a> 抽离若依的权限相关功能，以后做项目可以直接拿来用。</p><p>下面，来跟着我一起走读下 RuoYi-Vue 的权限相关功能。</p><h2 id="7-1-表结构"><a href="#7-1-表结构" class="headerlink" title="7.1. 表结构"></a>7.1. 表结构</h2><blockquote><p>对 RBAC 权限模型不了解的胖友，可以看看<a href="https://juejin.im/post/5d397e3ff265da1bca522011" target="_blank" rel="noopener">《到底什么是RBAC权限模型？！》</a>。</p></blockquote><table><thead><tr><th align="center">实体</th><th align="center">表</th><th align="center">说明</th></tr></thead><tbody><tr><td align="center">SysUser</td><td align="center"><code>sys_user</code></td><td align="center">用户信息</td></tr><tr><td align="center">SysRole</td><td align="center"><code>sys_role</code></td><td align="center">用户信息</td></tr><tr><td align="center">SysUserRole</td><td align="center"><code>sys_user_role</code></td><td align="center">用户和角色关联</td></tr><tr><td align="center">SysMenu</td><td align="center"><code>sys_menu</code></td><td align="center">菜单权限</td></tr><tr><td align="center">SysRoleMenu</td><td align="center"><code>sys_role_menu</code></td><td align="center">角色和菜单关联</td></tr></tbody></table><p>5 个表的关系比较简单，实际还有<code>SysDept</code>，<code>SysDeptRole</code>，逻辑与<code>SysUse</code>，<code>SysUserRole</code>一致，不再赘述：</p><ul><li>一个 SysUse ，可以拥有多个 SysRole ，通过 SysUserRole 存储关联。</li><li>一个 SysRole ，可以拥有多个 SysMenu ，通过 SysRoleMenu 存储关联。</li></ul><h3 id="7-1-1-SysUser"><a href="#7-1-1-SysUser" class="headerlink" title="7.1.1. SysUser"></a>7.1.1. SysUser</h3><p>用户实体类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysUser.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysUser</span> <span class="keyword">extends</span> <span class="title">BaseEntity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户ID */</span></span><br><span class="line">	<span class="keyword">private</span> Long userId;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 部门ID */</span></span><br><span class="line">	<span class="keyword">private</span> Long deptId;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 登录名称 */</span></span><br><span class="line">	<span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户名称 */</span></span><br><span class="line">	<span class="keyword">private</span> String nickName;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户邮箱 */</span></span><br><span class="line">	<span class="keyword">private</span> String email;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 手机号码 */</span></span><br><span class="line">	<span class="keyword">private</span> String phonenumber;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户性别 (0=男,1=女,2=未知) */</span></span><br><span class="line">	<span class="keyword">private</span> String sex;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户头像 */</span></span><br><span class="line">	<span class="keyword">private</span> String avatar;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 密码 */</span></span><br><span class="line">	<span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 盐加密 */</span></span><br><span class="line">	<span class="keyword">private</span> String salt;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 帐号状态 (0=正常,1=停用) */</span></span><br><span class="line">	<span class="keyword">private</span> String status;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 删除标志（0代表存在 2代表删除） */</span></span><br><span class="line">	<span class="keyword">private</span> String delFlag;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 最后登陆IP */</span></span><br><span class="line">	<span class="keyword">private</span> String loginIp;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 最后登陆时间 */</span></span><br><span class="line">	<span class="keyword">private</span> Date loginDate;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色对象 */</span></span><br><span class="line">	<span class="meta">@Transient</span></span><br><span class="line">	<span class="keyword">private</span> List&lt;SysRole&gt; roles;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 部门对象 */</span></span><br><span class="line">	<span class="keyword">private</span> SysDept dept;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色组 */</span></span><br><span class="line">	<span class="meta">@Transient</span></span><br><span class="line">	<span class="keyword">private</span> Long[] roleIds;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">SysUser</span><span class="params">()</span> </span>&#123; &#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">SysUser</span><span class="params">(Long userId)</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">this</span>.userId = userId;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAdmin</span><span class="params">()</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> isAdmin(<span class="keyword">this</span>.userId);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isAdmin</span><span class="params">(Long userId)</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> userId != <span class="keyword">null</span> &amp;&amp; <span class="number">1L</span> == userId;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 省略 toString</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>添加 <code>@Transient</code> 注解的字段，非存储字段。后续的实体，补充重复赘述。</li><li>每个字段比较简单，自己根据注释理解下即可。</li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">create table <span class="title">sys_user</span> <span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">  user_id           bigint(<span class="number">20</span>)</span>      not <span class="keyword">null</span> auto_increment    comment '用户ID',</span></span><br><span class="line"><span class="function">  dept_id           <span class="title">bigint</span><span class="params">(<span class="number">20</span>)</span>      <span class="keyword">default</span> <span class="keyword">null</span>               comment '部门ID',</span></span><br><span class="line"><span class="function">  user_name         <span class="title">varchar</span><span class="params">(<span class="number">30</span>)</span>     not <span class="keyword">null</span>                   comment '用户账号',</span></span><br><span class="line"><span class="function">  nick_name         <span class="title">varchar</span><span class="params">(<span class="number">30</span>)</span>     not <span class="keyword">null</span>                   comment '用户昵称',</span></span><br><span class="line"><span class="function">  user_type         <span class="title">varchar</span><span class="params">(<span class="number">2</span>)</span>      <span class="keyword">default</span> '00'               comment '用户类型（00系统用户）',</span></span><br><span class="line"><span class="function">  email             <span class="title">varchar</span><span class="params">(<span class="number">50</span>)</span>     <span class="keyword">default</span> ''                 comment '用户邮箱',</span></span><br><span class="line"><span class="function">  phonenumber       <span class="title">varchar</span><span class="params">(<span class="number">11</span>)</span>     <span class="keyword">default</span> ''                 comment '手机号码',</span></span><br><span class="line"><span class="function">  sex               <span class="title">char</span><span class="params">(<span class="number">1</span>)</span>         <span class="keyword">default</span> '0'                comment '用户性别（0男 1女 2未知）',</span></span><br><span class="line"><span class="function">  avatar            <span class="title">varchar</span><span class="params">(<span class="number">100</span>)</span>    <span class="keyword">default</span> ''                 comment '头像地址',</span></span><br><span class="line"><span class="function">  password          <span class="title">varchar</span><span class="params">(<span class="number">100</span>)</span>    <span class="keyword">default</span> ''                 comment '密码',</span></span><br><span class="line"><span class="function">  status            <span class="title">char</span><span class="params">(<span class="number">1</span>)</span>         <span class="keyword">default</span> '0'                comment '帐号状态（0正常 1停用）',</span></span><br><span class="line"><span class="function">  del_flag          <span class="title">char</span><span class="params">(<span class="number">1</span>)</span>         <span class="keyword">default</span> '0'                comment '删除标志（0代表存在 2代表删除）',</span></span><br><span class="line"><span class="function">  login_ip          <span class="title">varchar</span><span class="params">(<span class="number">50</span>)</span>     <span class="keyword">default</span> ''                 comment '最后登陆IP',</span></span><br><span class="line"><span class="function">  login_date        datetime                                   comment '最后登陆时间',</span></span><br><span class="line"><span class="function">  create_by         <span class="title">varchar</span><span class="params">(<span class="number">64</span>)</span>     <span class="keyword">default</span> ''                 comment '创建者',</span></span><br><span class="line"><span class="function">  create_time       datetime                                   comment '创建时间',</span></span><br><span class="line"><span class="function">  update_by         <span class="title">varchar</span><span class="params">(<span class="number">64</span>)</span>     <span class="keyword">default</span> ''                 comment '更新者',</span></span><br><span class="line"><span class="function">  update_time       datetime                                   comment '更新时间',</span></span><br><span class="line"><span class="function">  remark            <span class="title">varchar</span><span class="params">(<span class="number">500</span>)</span>    <span class="keyword">default</span> <span class="keyword">null</span>               comment '备注',</span></span><br><span class="line"><span class="function">  primary <span class="title">key</span> <span class="params">(user_id)</span></span></span><br><span class="line"><span class="function">) engine</span>=innodb auto_increment=<span class="number">100</span> comment = <span class="string">'用户信息表'</span>;</span><br></pre></td></tr></table></figure><h3 id="7-1-2-SysRole"><a href="#7-1-2-SysRole" class="headerlink" title="7.1.2. SysRole"></a>7.1.2. SysRole</h3><p>角色实体类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysRole.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysRole</span> <span class="keyword">extends</span> <span class="title">BaseEntity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色序号 */</span></span><br><span class="line">	<span class="keyword">private</span> Long roleId;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色名称 */</span></span><br><span class="line">	<span class="keyword">private</span> String roleName;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色权限 */</span></span><br><span class="line">	<span class="keyword">private</span> String roleKey;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色排序 */</span></span><br><span class="line">	<span class="keyword">private</span> String roleSort;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 数据范围（1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限） */</span></span><br><span class="line">	<span class="keyword">private</span> String dataScope;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 角色状态（0=正常,1=停用） */</span></span><br><span class="line">	<span class="keyword">private</span> String status;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 删除标志（0代表存在 2代表删除） */</span></span><br><span class="line">	<span class="keyword">private</span> String delFlag;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 用户是否存在此角色标识 默认不存在 */</span></span><br><span class="line">	<span class="meta">@Transient</span></span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 菜单组 */</span></span><br><span class="line">	<span class="meta">@Transient</span></span><br><span class="line">	<span class="keyword">private</span> Long[] menuIds;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/** 部门组（数据权限） */</span></span><br><span class="line">	<span class="meta">@Transient</span></span><br><span class="line">	<span class="keyword">private</span> Long[] deptIds;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">SysRole</span><span class="params">()</span> </span>&#123; &#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">SysRole</span><span class="params">(Long roleId)</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">this</span>.roleId = roleId;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAdmin</span><span class="params">()</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> isAdmin(<span class="keyword">this</span>.roleId);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isAdmin</span><span class="params">(Long roleId)</span></span></span><br><span class="line"><span class="function">	</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> roleId != <span class="keyword">null</span> &amp;&amp; <span class="number">1L</span> == roleId;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>每个字段比较简单，自己根据注释理解下即可。</li><li><code>roleKey</code> 属性，对应的角色<strong>标识</strong>字符串，可以对应多个角色<strong>标识</strong>，使用逗号分隔。例如说：<code>&quot;admin,normal&quot;</code> 。</li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> sys_role (</span><br><span class="line">  role_id           <span class="built_in">bigint</span>(<span class="number">20</span>)      <span class="keyword">not</span> <span class="literal">null</span> auto_increment    <span class="keyword">comment</span> <span class="string">'角色ID'</span>,</span><br><span class="line">  role_name         <span class="built_in">varchar</span>(<span class="number">30</span>)     <span class="keyword">not</span> <span class="literal">null</span>                   <span class="keyword">comment</span> <span class="string">'角色名称'</span>,</span><br><span class="line">  role_key          <span class="built_in">varchar</span>(<span class="number">100</span>)    <span class="keyword">not</span> <span class="literal">null</span>                   <span class="keyword">comment</span> <span class="string">'角色权限字符串'</span>,</span><br><span class="line">  role_sort         <span class="built_in">int</span>(<span class="number">4</span>)          <span class="keyword">not</span> <span class="literal">null</span>                   <span class="keyword">comment</span> <span class="string">'显示顺序'</span>,</span><br><span class="line">  data_scope        <span class="built_in">char</span>(<span class="number">1</span>)         <span class="keyword">default</span> <span class="string">'1'</span>                <span class="keyword">comment</span> <span class="string">'数据范围（1：全部数据权限 2：自定数据权限 3：本部门数据权限 4：本部门及以下数据权限）'</span>,</span><br><span class="line">  <span class="keyword">status</span>            <span class="built_in">char</span>(<span class="number">1</span>)         <span class="keyword">not</span> <span class="literal">null</span>                   <span class="keyword">comment</span> <span class="string">'角色状态（0正常 1停用）'</span>,</span><br><span class="line">  del_flag          <span class="built_in">char</span>(<span class="number">1</span>)         <span class="keyword">default</span> <span class="string">'0'</span>                <span class="keyword">comment</span> <span class="string">'删除标志（0代表存在 2代表删除）'</span>,</span><br><span class="line">  create_by         <span class="built_in">varchar</span>(<span class="number">64</span>)     <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'创建者'</span>,</span><br><span class="line">  create_time       datetime                                   <span class="keyword">comment</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  update_by         <span class="built_in">varchar</span>(<span class="number">64</span>)     <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'更新者'</span>,</span><br><span class="line">  update_time       datetime                                   <span class="keyword">comment</span> <span class="string">'更新时间'</span>,</span><br><span class="line">  remark            <span class="built_in">varchar</span>(<span class="number">500</span>)    <span class="keyword">default</span> <span class="literal">null</span>               <span class="keyword">comment</span> <span class="string">'备注'</span>,</span><br><span class="line">  primary <span class="keyword">key</span> (role_id)</span><br><span class="line">) <span class="keyword">engine</span>=<span class="keyword">innodb</span> auto_increment=<span class="number">100</span> <span class="keyword">comment</span> = <span class="string">'角色信息表'</span>;</span><br></pre></td></tr></table></figure><h3 id="7-1-3-SysUserRole"><a href="#7-1-3-SysUserRole" class="headerlink" title="7.1.3. SysUserRole"></a>7.1.3. SysUserRole</h3><p>用户和角色关联实体类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysUserRole.java</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysUserRole</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 用户ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long userId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 角色ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long roleId;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>每个字段比较简单，自己根据注释理解下即可。</li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> sys_user_role (</span><br><span class="line">  user_id   <span class="built_in">bigint</span>(<span class="number">20</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">'用户ID'</span>,</span><br><span class="line">  role_id   <span class="built_in">bigint</span>(<span class="number">20</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">'角色ID'</span>,</span><br><span class="line">  primary <span class="keyword">key</span>(user_id, role_id)</span><br><span class="line">) <span class="keyword">engine</span>=<span class="keyword">innodb</span> <span class="keyword">comment</span> = <span class="string">'用户和角色关联表'</span>;</span><br></pre></td></tr></table></figure><h3 id="7-1-4-SysMenu"><a href="#7-1-4-SysMenu" class="headerlink" title="7.1.4. SysMenu"></a>7.1.4. SysMenu</h3><p>菜单权限实体类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysMenu.java</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysMenu</span> <span class="keyword">extends</span> <span class="title">BaseEntity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 菜单ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long menuId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 菜单名称 */</span></span><br><span class="line">    <span class="keyword">private</span> String menuName;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 父菜单名称 */</span></span><br><span class="line">    <span class="keyword">private</span> String parentName;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 父菜单ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long parentId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 显示顺序 */</span></span><br><span class="line">    <span class="keyword">private</span> String orderNum;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 路由地址 */</span></span><br><span class="line">    <span class="keyword">private</span> String path;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 组件路径 */</span></span><br><span class="line">    <span class="keyword">private</span> String component;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 是否为外链（0是 1否） */</span></span><br><span class="line">    <span class="keyword">private</span> String isFrame;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 类型（M目录 C菜单 F按钮） */</span></span><br><span class="line">    <span class="keyword">private</span> String menuType;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 菜单状态:0显示,1隐藏 */</span></span><br><span class="line">    <span class="keyword">private</span> String visible;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 权限字符串 */</span></span><br><span class="line">    <span class="keyword">private</span> String perms;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 菜单图标 */</span></span><br><span class="line">    <span class="keyword">private</span> String icon;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 子菜单 */</span></span><br><span class="line">    <span class="meta">@Transient</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;SysMenu&gt; children = <span class="keyword">new</span> ArrayList&lt;SysMenu&gt;();</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><strong>菜单仅仅是权限的一种展现形式，实际业务根据需要去定义角色的权限对应的实体形式</strong>。</p></li><li><p>每个字段比较简单，自己根据资源理解下即可。我们来重点看几个字段。</p></li><li><p><code>menuType</code> 属性，定义了三种类型。其中，<code>F</code> 代表按钮，是为了做页面中的功能级的权限。</p></li><li><p><code>perms</code> 属性，对应的权限<strong>标识</strong>字符串。一般格式为 <code>${大模块}:${小模块}:{操作}</code> 。示例如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">用户查询：system:user:query</span><br><span class="line">用户新增：system:user:add</span><br><span class="line">用户修改：system:user:edit</span><br><span class="line">用户删除：system:user:remove</span><br><span class="line">用户导出：system:user:<span class="built_in">export</span></span><br><span class="line">用户导入：system:user:import</span><br><span class="line">重置密码：system:user:resetPwd</span><br></pre></td></tr></table></figure><ul><li>对于前端来说，每个按钮在展示时，可以判断用户是否有该按钮的权限。如果没有，则进行隐藏。当然，前端在首次进入系统的时候，会请求一次权限列表到本地进行缓存。</li><li>对于后端来说，每个接口上会添加 <code>@PreAuthorize(&quot;@ss.hasPermi(&#39;system:user:list&#39;)&quot;)</code> 注解。在请求接口时，会校验用户是否有该 URL 对应的权限。如果没有，则会抛出权限验证失败的异常。</li><li>一个 <code>perms</code> 属性，可以对应多个权限<strong>标识</strong>，使用逗号分隔。例如说：<code>&quot;system:user:query,system:user:add&quot;</code> 。</li></ul></li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> sys_menu (</span><br><span class="line">  menu_id           <span class="built_in">bigint</span>(<span class="number">20</span>)      <span class="keyword">not</span> <span class="literal">null</span> auto_increment    <span class="keyword">comment</span> <span class="string">'菜单ID'</span>,</span><br><span class="line">  menu_name         <span class="built_in">varchar</span>(<span class="number">50</span>)     <span class="keyword">not</span> <span class="literal">null</span>                   <span class="keyword">comment</span> <span class="string">'菜单名称'</span>,</span><br><span class="line">  parent_id         <span class="built_in">bigint</span>(<span class="number">20</span>)      <span class="keyword">default</span> <span class="number">0</span>                  <span class="keyword">comment</span> <span class="string">'父菜单ID'</span>,</span><br><span class="line">  order_num         <span class="built_in">int</span>(<span class="number">4</span>)          <span class="keyword">default</span> <span class="number">0</span>                  <span class="keyword">comment</span> <span class="string">'显示顺序'</span>,</span><br><span class="line">  <span class="keyword">path</span>              <span class="built_in">varchar</span>(<span class="number">200</span>)    <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'路由地址'</span>,</span><br><span class="line">  component         <span class="built_in">varchar</span>(<span class="number">255</span>)    <span class="keyword">default</span> <span class="literal">null</span>               <span class="keyword">comment</span> <span class="string">'组件路径'</span>,</span><br><span class="line">  is_frame          <span class="built_in">int</span>(<span class="number">1</span>)          <span class="keyword">default</span> <span class="number">1</span>                  <span class="keyword">comment</span> <span class="string">'是否为外链（0是 1否）'</span>,</span><br><span class="line">  menu_type         <span class="built_in">char</span>(<span class="number">1</span>)         <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'菜单类型（M目录 C菜单 F按钮）'</span>,</span><br><span class="line">  <span class="keyword">visible</span>           <span class="built_in">char</span>(<span class="number">1</span>)         <span class="keyword">default</span> <span class="number">0</span>                  <span class="keyword">comment</span> <span class="string">'菜单状态（0显示 1隐藏）'</span>,</span><br><span class="line">  perms             <span class="built_in">varchar</span>(<span class="number">100</span>)    <span class="keyword">default</span> <span class="literal">null</span>               <span class="keyword">comment</span> <span class="string">'权限标识'</span>,</span><br><span class="line">  icon              <span class="built_in">varchar</span>(<span class="number">100</span>)    <span class="keyword">default</span> <span class="string">'#'</span>                <span class="keyword">comment</span> <span class="string">'菜单图标'</span>,</span><br><span class="line">  create_by         <span class="built_in">varchar</span>(<span class="number">64</span>)     <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'创建者'</span>,</span><br><span class="line">  create_time       datetime                                   <span class="keyword">comment</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  update_by         <span class="built_in">varchar</span>(<span class="number">64</span>)     <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'更新者'</span>,</span><br><span class="line">  update_time       datetime                                   <span class="keyword">comment</span> <span class="string">'更新时间'</span>,</span><br><span class="line">  remark            <span class="built_in">varchar</span>(<span class="number">500</span>)    <span class="keyword">default</span> <span class="string">''</span>                 <span class="keyword">comment</span> <span class="string">'备注'</span>,</span><br><span class="line">  primary <span class="keyword">key</span> (menu_id)</span><br><span class="line">) <span class="keyword">engine</span>=<span class="keyword">innodb</span> auto_increment=<span class="number">2000</span> <span class="keyword">comment</span> = <span class="string">'菜单权限表'</span>;</span><br></pre></td></tr></table></figure><h3 id="7-1-5-SysRoleMenu"><a href="#7-1-5-SysRoleMenu" class="headerlink" title="7.1.5. SysRoleMenu"></a>7.1.5. SysRoleMenu</h3><p>菜单权限实体类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysRoleMenu.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysRoleMenu</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/** 角色ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long roleId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 菜单ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long menuId;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>每个字段比较简单，自己根据注释理解下即可。</li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> sys_role_menu (</span><br><span class="line">  role_id   <span class="built_in">bigint</span>(<span class="number">20</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">'角色ID'</span>,</span><br><span class="line">  menu_id   <span class="built_in">bigint</span>(<span class="number">20</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">'菜单ID'</span>,</span><br><span class="line">  primary <span class="keyword">key</span>(role_id, menu_id)</span><br><span class="line">) <span class="keyword">engine</span>=<span class="keyword">innodb</span> <span class="keyword">comment</span> = <span class="string">'角色和菜单关联表'</span>;</span><br></pre></td></tr></table></figure><h2 id="7-2-SecurityConfig"><a href="#7-2-SecurityConfig" class="headerlink" title="7.2. SecurityConfig"></a>7.2. SecurityConfig</h2><p>配置类，继承 WebSecurityConfigurerAdapter 抽象类，实现 Spring Security 在 Web 场景下的自定义配置。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>涉及到的配置方法较多，我们逐个来看看。</li></ul><p>重写 <code>#configure(AuthenticationManagerBuilder auth)</code> 方法，实现 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/authentication/AuthenticationManager.java" target="_blank" rel="noopener">AuthenticationManager</a> 认证管理器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 自定义用户认证逻辑</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> UserDetailsService userDetailsService;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 身份认证接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthenticationManagerBuilder auth)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    auth.userDetailsService(userDetailsService) <span class="comment">// &lt;X&gt;</span></span><br><span class="line">            .passwordEncoder(bCryptPasswordEncoder()); <span class="comment">// &lt;Y&gt;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 强散列哈希加密实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> BCryptPasswordEncoder <span class="title">bCryptPasswordEncoder</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>`` 处，调用 <code>AuthenticationManagerBuilder#userDetailsService(userDetailsService)</code> 方法，使用自定义实现的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetailsService.java" target="_blank" rel="noopener">UserDetailsService</a> 实现类，更加<strong>灵活</strong>且<strong>自由</strong>的实现认证的用户信息的读取。在<a href="#anchor-7.3.1">「7.3.1 加载用户信息」</a>中，我们会看到 RuoYi-Vue 对 UserDetailsService 的自定义实现类。</li><li>`` 处，调用 <code>AbstractDaoAuthenticationConfigurer#passwordEncoder(passwordEncoder)</code> 方法，设置 PasswordEncoder 密码编码器。这里，就使用了 bCryptPasswordEncoder 强散列哈希加密实现。</li></ul><p>重写 <code>#configure(HttpSecurity httpSecurity)</code> 方法，主要配置 URL 的权限控制。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 认证失败处理类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> AuthenticationEntryPointImpl unauthorizedHandler;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 退出处理类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> LogoutSuccessHandlerImpl logoutSuccessHandler;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * token 认证过滤器</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> JwtAuthenticationTokenFilter authenticationTokenFilter;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity httpSecurity)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    httpSecurity</span><br><span class="line">            <span class="comment">// CRSF禁用，因为不使用session</span></span><br><span class="line">            .csrf().disable()</span><br><span class="line">            <span class="comment">// &lt;X&gt; 认证失败处理类</span></span><br><span class="line">            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()</span><br><span class="line">            <span class="comment">// 基于token，所以不需要session</span></span><br><span class="line">            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()</span><br><span class="line">            <span class="comment">// 过滤请求</span></span><br><span class="line">            .authorizeRequests()</span><br><span class="line">            <span class="comment">// &lt;Y&gt; 对于登录login 验证码captchaImage 允许匿名访问</span></span><br><span class="line">            .antMatchers(<span class="string">"/login"</span>, <span class="string">"/captchaImage"</span>).anonymous()</span><br><span class="line">            .antMatchers(</span><br><span class="line">                    HttpMethod.GET,</span><br><span class="line">                    <span class="string">"/*.html"</span>,</span><br><span class="line">                    <span class="string">"/**/*.html"</span>,</span><br><span class="line">                    <span class="string">"/**/*.css"</span>,</span><br><span class="line">                    <span class="string">"/**/*.js"</span></span><br><span class="line">            ).permitAll()</span><br><span class="line">            .antMatchers(<span class="string">"/profile/**"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/common/download**"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/swagger-ui.html"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/swagger-resources/**"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/webjars/**"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/*/api-docs"</span>).anonymous()</span><br><span class="line">            .antMatchers(<span class="string">"/druid/**"</span>).anonymous()</span><br><span class="line">            <span class="comment">// 除上面外的所有请求全部需要鉴权认证</span></span><br><span class="line">            .anyRequest().authenticated()</span><br><span class="line">            .and()</span><br><span class="line">            .headers().frameOptions().disable();</span><br><span class="line">  	<span class="comment">// &lt;Z&gt; 登出处理器</span></span><br><span class="line">    httpSecurity.logout().logoutUrl(<span class="string">"/logout"</span>).logoutSuccessHandler(logoutSuccessHandler); </span><br><span class="line">    <span class="comment">// &lt;P&gt; 添加 JWT filter</span></span><br><span class="line">    httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>比较长，我们选择重点的来看。</li><li><code>X</code> 处，设置认证失败时的处理器为 <code>unauthorizedHandler</code> 。详细解析，见<a href="#anchor-7.6.1">「7.6.1 AuthenticationEntryPointImpl」</a>。</li><li><code>Y</code> 处，设置用于登陆的 <code>/login</code> 接口，允许匿名访问。这样，后续我们就可以使用自定义的登陆接口。详细解析，见<a href="#anchor-7.3">「7.3 登陆 API 接口」</a>。</li><li><code>Z</code> 处，设置登出成功的处理器为 <code>logoutSuccessHandler</code> 。详细解析，见<a href="#anchor-7.6.3">「7.6.3 LogoutSuccessHandlerImpl」</a>。<ul><li>注意⚠️：如果调用 <code>logout</code> 接口，未添加自定义的 <code>header</code>，也能返回登出成功，但实际并没有进入自定义的登出处理器，登陆的用户也没有登出成功</li></ul></li><li><code>P</code> 处，添加 JWT 认证过滤器 <code>authenticationTokenFilter</code> ，用于用户使用用户名与密码登陆完成后，后续请求基于 JWT 来认证。 详细解析，见<a href="#anchor-7.4">「7.4 JwtAuthenticationTokenFilter」</a>。</li></ul><p>重写 <code>#authenticationManagerBean</code> 方法，解决无法直接注入 AuthenticationManager 的问题。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SecurityConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> AuthenticationManager <span class="title">authenticationManagerBean</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.authenticationManagerBean();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在方法上，额外添加了 <code>@Bean</code> 注解，保证创建出 AuthenticationManager Bean 。</li></ul><p>下面，我们详细的来看看，各个配置的 Bean 的逻辑。</p><h2 id="7-3-登陆-API-接口"><a href="#7-3-登陆-API-接口" class="headerlink" title="7.3. 登陆 API 接口"></a><span id="anchor-7.3">7.3. 登陆 API 接口</span></h2><p><strong>SysLoginController#login(…)</strong></p><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/SysLoginController.java" target="_blank" rel="noopener">SysLoginController</a> 中，定义了 <code>/login</code> 接口，提供登陆功能。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysLoginController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SysLoginService loginService;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录方法</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> password 密码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> code 验证码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> uuid 唯一标识</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 结果</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@PostMapping</span>(<span class="string">"/login"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> AjaxResult <span class="title">login</span><span class="params">(String username, String password, String code, String uuid)</span> </span>&#123;</span><br><span class="line">    AjaxResult ajax = AjaxResult.success();</span><br><span class="line">    <span class="comment">// 生成令牌</span></span><br><span class="line">    String token = loginService.login(username, password, code, uuid);</span><br><span class="line">    ajax.put(Constants.TOKEN, token);</span><br><span class="line">    <span class="keyword">return</span> ajax;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>在内部，会调用 <code>loginService#login(username, password, code, uuid)</code> 方法，会进行基于用户名与密码的登陆认证。认证通过后，返回身份 TOKEN 。</p></li><li><p>登陆成功后，该接口响应示例如下</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"msg"</span>: <span class="string">"操作成功"</span>, </span><br><span class="line">    <span class="attr">"code"</span>: <span class="number">200</span>, </span><br><span class="line">    <span class="attr">"token"</span>: <span class="string">"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImJkN2Q4OTZiLTU2NTAtNGIyZS1iNjFjLTc0MjlkYmRkNzA1YyJ9.lkU8ot4GecLHs7VAcRAo1fLMOaFryd4W5Q_a2wzPwcOL0Kiwyd4enpnGd79A_aQczXC-JB8vELNcNn7BrtJn9A"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>后续，前端在请求后端接口时，在请求头上带头该 <code>token</code> 值，作为用户身份标识。</li></ul></li></ul><p><strong>SysLoginService#login(…)</strong></p><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/service/impl/SysLoginServiceImpl.java" target="_blank" rel="noopener">SysLoginServiceImpl</a> 中，定义了 <code>#login(username, password, code, uuid)</code> 方法，进行基于用户名与密码的登陆认证。认证通过后，返回身份 TOKEN 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysLoginServiceImpl.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> TokenService tokenService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> AuthenticationManager authenticationManager;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisCache redisCache;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录验证</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> password 密码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> code     验证码</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> uuid     唯一标识</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 结果</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">login</span><span class="params">(String username, String password, String code, String uuid)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 验证图片验证码的正确性</span></span><br><span class="line">    String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; <span class="comment">// uuid 的作用，是获得对应的图片验证码</span></span><br><span class="line">    String captcha = redisCache.getCacheObject(verifyKey); <span class="comment">// 从 Redis 中，获得图片验证码</span></span><br><span class="line">    redisCache.deleteObject(verifyKey); <span class="comment">// 从 Redis 中，删除图片验证码</span></span><br><span class="line">    <span class="keyword">if</span> (captcha == <span class="keyword">null</span>) &#123; <span class="comment">// 图片验证码不存在</span></span><br><span class="line">        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(<span class="string">"user.jcaptcha.error"</span>)));</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> CaptchaExpireException();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!code.equalsIgnoreCase(captcha)) &#123; <span class="comment">// 图片验证码不正确</span></span><br><span class="line">        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(<span class="string">"user.jcaptcha.expire"</span>)));</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> CaptchaException();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// &lt;2&gt; 用户验证</span></span><br><span class="line">    Authentication authentication;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 该方法会去调用 UserDetailsServiceImpl.loadUserByUsername</span></span><br><span class="line">        authentication = authenticationManager</span><br><span class="line">                .authenticate(<span class="keyword">new</span> UsernamePasswordAuthenticationToken(username, password));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="comment">// &lt;2.1&gt; 发生异常，说明验证不通过，记录相应的登陆失败日志</span></span><br><span class="line">        <span class="keyword">if</span> (e <span class="keyword">instanceof</span> BadCredentialsException) &#123;</span><br><span class="line">            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(<span class="string">"user.password.not.match"</span>)));</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> UserPasswordNotMatchException();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> CustomException(e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// &lt;2.2&gt; 验证通过，记录相应的登陆成功日志</span></span><br><span class="line">    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message(<span class="string">"user.login.success"</span>)));</span><br><span class="line">    <span class="comment">// &lt;3&gt; 生成 Token</span></span><br><span class="line">    LoginUser loginUser = (LoginUser) authentication.getPrincipal();</span><br><span class="line">    <span class="keyword">return</span> tokenService.createToken(loginUser);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，验证图片验证码的正确性。该验证码会存储在 Redis 缓存中，通过 <code>uuid</code> 作为对应的标识。生成的逻辑，自己看 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/CaptchaController.java" target="_blank" rel="noopener">CaptchaController</a> 提供的 <code>/captchaImage</code> 接口，这种模版代码完全可以拿来主义。</li><li><code>&lt;2&gt;</code> 处，调用 Spring Security 的AuthenticationManager的<code>#authenticate(UsernamePasswordAuthenticationToken authentication)</code>方法，基于用户名与密码的登陆认证。在其内部，会调用我们定义的 UserDetailsServiceImpl 的<code>#loadUserByUsername(String username)</code>方法，获得指定用户名对应的用户信息。详细解析，见「7.3.1 加载用户信息」(#anchor-7.3.1)。<ul><li><code>&lt;2.1&gt;</code> 处，发生异常，说明认证<strong>不</strong>通过，记录相应的登陆失败日志。</li><li><code>&lt;2.2&gt;</code> 处，<strong>未</strong>发生异常，说明认证通过，记录相应的登陆成功日志。</li><li>关于上述日志，我们在<a href="#anchor-7.7">「7.7 登陆日志」</a>来讲。</li></ul></li><li><code>&lt;3&gt;</code> 处，调用 TokenService 的 <code>#createToken(LoginUser loginUser)</code> 方法，给认证通过的用户，生成其对应的认证 TOKEN 。这样，该用户的后续请求，就使用该 TOKEN 作为身份标识进行认证。</li></ul><h3 id="7-3-1-加载用户信息"><a href="#7-3-1-加载用户信息" class="headerlink" title="7.3.1. 加载用户信息"></a><span id="anchor-7.3.1">7.3.1. 加载用户信息</span></h3><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/UserDetailsServiceImpl.java" target="_blank" rel="noopener">UserDetailsServiceImpl</a> 中，实现 Spring Security <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetailsService.java" target="_blank" rel="noopener">UserDetailsService</a> 接口，实现了该接口定义的 <code>#loadUserByUsername(String username)</code> 方法，获得指定用户名对应的用户信息。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserDetailsServiceImpl.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ISysUserService userService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SysPermissionService permissionService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> UserDetails <span class="title">loadUserByUsername</span><span class="params">(String username)</span> <span class="keyword">throws</span> UsernameNotFoundException </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 查询指定用户名对应的 SysUser</span></span><br><span class="line">    SysUser user = userService.selectUserByUserName(username);</span><br><span class="line">    <span class="comment">// &lt;2&gt; 各种校验</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNull(user)) &#123;</span><br><span class="line">        log.info(<span class="string">"登录用户：&#123;&#125; 不存在."</span>, username);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> UsernameNotFoundException(<span class="string">"登录用户："</span> + username + <span class="string">" 不存在"</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (UserStatus.DELETED.getCode().equals(user.getDelFlag())) &#123;</span><br><span class="line">        log.info(<span class="string">"登录用户：&#123;&#125; 已被删除."</span>, username);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> BaseException(<span class="string">"对不起，您的账号："</span> + username + <span class="string">" 已被删除"</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (UserStatus.DISABLE.getCode().equals(user.getStatus())) &#123;</span><br><span class="line">        log.info(<span class="string">"登录用户：&#123;&#125; 已被停用."</span>, username);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> BaseException(<span class="string">"对不起，您的账号："</span> + username + <span class="string">" 已停用"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;3&gt; 创建 Spring Security UserDetails 用户明细</span></span><br><span class="line">    <span class="keyword">return</span> createLoginUser(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> UserDetails <span class="title">createLoginUser</span><span class="params">(SysUser user)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> LoginUser(user, permissionService.getMenuPermission(user));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，调用 ISysUserService 的 <code>#selectUserByUserName(String userName)</code> 方法，查询指定用户名对应的 SysUser 。代码如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysUserServiceImpl.java</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SysUserMapper userMapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> SysUser <span class="title">selectUserByUserName</span><span class="params">(String userName)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.selectUserByUserName(userName);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SysUserMapper.XML</span></span><br><span class="line">&lt;sql id=<span class="string">"selectUserVo"</span>&gt;</span><br><span class="line">    select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,</span><br><span class="line">    d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,</span><br><span class="line">    r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status</span><br><span class="line">    from sys_user u</span><br><span class="line">	    left join sys_dept d on u.dept_id = d.dept_id</span><br><span class="line">	    left join sys_user_role ur on u.user_id = ur.user_id</span><br><span class="line">	    left join sys_role r on r.role_id = ur.role_id</span><br><span class="line">&lt;/sql&gt;</span><br><span class="line"></span><br><span class="line">&lt;select id=<span class="string">"selectUserByUserName"</span> parameterType=<span class="string">"String"</span> resultMap=<span class="string">"SysUserResult"</span>&gt;</span><br><span class="line">    &lt;include refid=<span class="string">"selectUserVo"</span>/&gt;</span><br><span class="line">	where u.user_name = #&#123;userName&#125;</span><br><span class="line">&lt;/select&gt;</span><br></pre></td></tr></table></figure><ul><li>通过查询 <code>sys_user</code> 表，同时连接 <code>sys_dept</code>、<code>sys_user_role</code>、<code>sys_role</code> 表，将 <code>username</code> 对应的 SysUser 相关信息都一次性查询出来。</li><li>返回结果 <code>SysUserResult</code> 的具体定义，点击 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/resources/mapper/SysUserMapper.xml#L7-L46" target="_blank" rel="noopener">传送门</a> 查看，实际就是 SysUser 实体类。</li></ul></li><li><p><code>&lt;2&gt;</code> 处，各种校验。如果校验不通过，抛出 UsernameNotFoundException 或 BaseException 异常。</p></li><li><p><code>&lt;3&gt;</code> 处，调用 SysPermissionService 的 <code>#getMenuPermission(SysUser user)</code> 方法，获得用户的 SysRoleMenu 的权限<strong>标识</strong>字符串的集合。代码如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysPermissionService.java</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ISysMenuService menuService;</span><br><span class="line">  </span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;String&gt; <span class="title">getMenuPermission</span><span class="params">(SysUser user)</span> </span>&#123;</span><br><span class="line">    Set&lt;String&gt; roles = <span class="keyword">new</span> HashSet&lt;String&gt;();</span><br><span class="line">    <span class="comment">// 管理员拥有所有权限</span></span><br><span class="line">    <span class="keyword">if</span> (user.isAdmin()) &#123;</span><br><span class="line">        roles.add(<span class="string">"*:*:*"</span>); <span class="comment">// 所有模块</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 读取</span></span><br><span class="line">        roles.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> roles;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SysMenuServiceImpl.java</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SysMenuMapper menuMapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;String&gt; <span class="title">selectMenuPermsByUserId</span><span class="params">(Long userId)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 读取 SysMenu 的权限标识数组</span></span><br><span class="line">    List&lt;String&gt; perms = menuMapper.selectMenuPermsByUserId(userId);</span><br><span class="line">    <span class="comment">// 逐个，按照“逗号”分隔</span></span><br><span class="line">    Set&lt;String&gt; permsSet = <span class="keyword">new</span> HashSet&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (String perm : perms) &#123;</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotEmpty(perm)) &#123;</span><br><span class="line">            permsSet.addAll(Arrays.asList(perm.trim().split(<span class="string">","</span>)));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> permsSet;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SysMenuMapper.xml</span></span><br><span class="line">&lt;select id=<span class="string">"selectMenuPermsByUserId"</span> parameterType=<span class="string">"Long"</span> resultType=<span class="string">"String"</span>&gt;</span><br><span class="line">	select distinct m.perms</span><br><span class="line">	from sys_menu m</span><br><span class="line">		 left join sys_role_menu rm on m.menu_id = rm.menu_id</span><br><span class="line">		 left join sys_user_role ur on rm.role_id = ur.role_id</span><br><span class="line">	where ur.user_id = #&#123;userId&#125;</span><br><span class="line">&lt;/select&gt;</span><br></pre></td></tr></table></figure><ul><li>虽然代码很长，但是核心的并不多。</li><li>首先，如果 SysUser 是超级管理员，则其权限标识集合就是 <code>*:*:*</code> ，标识可以所有模块的所有操作。</li><li>然后，查询 <code>sys_menu</code> 表，同时连接 <code>sys_role_menu</code>、<code>sys_user_role</code> 表，将 SysUser 拥有的 SysMenu 的权限标识数组，然后使用 <code>&quot;,&quot;</code> 分隔每个 SysMenu 对应的权限标识。</li></ul></li></ul><p>这里，我们看到最终返回的是 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/domain/LoginUser.java" target="_blank" rel="noopener">LoginUser</a> ，实现 Spring Security <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java" target="_blank" rel="noopener">UserDetails</a> 接口，自定义的用户明细。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LoginUser.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoginUser</span> <span class="keyword">implements</span> <span class="title">UserDetails</span> </span>&#123;</span><br><span class="line">   </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 用户唯一标识 */</span></span><br><span class="line">    <span class="keyword">private</span> String token;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登陆时间 */</span></span><br><span class="line">    <span class="keyword">private</span> Long loginTime;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 过期时间 */</span></span><br><span class="line">    <span class="keyword">private</span> Long expireTime;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登录IP地址 */</span></span><br><span class="line">    <span class="keyword">private</span> String ipaddr;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登录地点 */</span></span><br><span class="line">    <span class="keyword">private</span> String loginLocation;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 浏览器类型 */</span></span><br><span class="line">    <span class="keyword">private</span> String browser;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 操作系统 */</span></span><br><span class="line">    <span class="keyword">private</span> String os;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 权限列表 */</span></span><br><span class="line">    <span class="keyword">private</span> Set&lt;String&gt; permissions;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 用户信息 */</span></span><br><span class="line">    <span class="keyword">private</span> SysUser user;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-3-2-创建认证-Token"><a href="#7-3-2-创建认证-Token" class="headerlink" title="7.3.2. 创建认证 Token"></a>7.3.2. 创建认证 Token</h3><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/TokenService.java" target="_blank" rel="noopener">TokenService</a> 中，定义了 <code>#createToken(LoginUser loginUser)</code> 方法，给认证通过的用户，生成其对应的认证 Token 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建令牌</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> loginUser 用户信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 令牌</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">createToken</span><span class="params">(LoginUser loginUser)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 设置 LoginUser 的用户唯一标识。注意，这里虽然变量名叫 token ，其实不是身份认证的 Token</span></span><br><span class="line">    String token = IdUtils.fastUUID();</span><br><span class="line">    loginUser.setToken(token);</span><br><span class="line">    <span class="comment">// &lt;2&gt; 设置用户终端相关的信息，包括 IP、城市、浏览器、操作系统</span></span><br><span class="line">    setUserAgent(loginUser);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;3&gt; 记录缓存</span></span><br><span class="line">    refreshToken(loginUser);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;4&gt; 生成 JWT 的 Token</span></span><br><span class="line">    Map&lt;String, Object&gt; claims = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">    claims.put(Constants.LOGIN_USER_KEY, token);</span><br><span class="line">    <span class="keyword">return</span> createToken(claims);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>注意，这个方法不仅仅会生成认证 Token ，还会缓存 LoginUser 到 Redis 缓存中。</p></li><li><p><code>&lt;1&gt;</code> 处，设置 LoginUser 的用户唯一标识，即 <code>LoginUser.token</code>。注意，这里虽然变量名叫 <code>token</code> ，其实不是身份认证的 Token 。</p></li><li><p><code>&lt;2&gt;</code> 处，调用 <code>#setUserAgent(LoginUser loginUser)</code> 方法，设置用户终端相关的信息，包括 IP、城市、浏览器、操作系统。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUserAgent</span><span class="params">(LoginUser loginUser)</span> </span>&#123;</span><br><span class="line">    UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader(<span class="string">"User-Agent"</span>));</span><br><span class="line">    String ip = IpUtils.getIpAddr(ServletUtils.getRequest());</span><br><span class="line">    loginUser.setIpaddr(ip);</span><br><span class="line">    loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));</span><br><span class="line">    loginUser.setBrowser(userAgent.getBrowser().getName());</span><br><span class="line">    loginUser.setOs(userAgent.getOperatingSystem().getName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><code>&lt;3&gt;</code> 处，调用 <code>#refreshToken(LoginUser loginUser)</code> 方法，缓存 LoginUser 到 Redis 缓存中。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// application.yaml</span></span><br><span class="line"># token配置</span><br><span class="line">token:</span><br><span class="line">    # 令牌有效期（默认30分钟）</span><br><span class="line">    expireTime: <span class="number">30</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Constants.java</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录用户 redis key</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String LOGIN_TOKEN_KEY = <span class="string">"login_tokens:"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"><span class="comment">// 令牌有效期（默认30分钟）</span></span><br><span class="line"><span class="meta">@Value</span>(<span class="string">"$&#123;token.expireTime&#125;"</span>)</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> expireTime;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisCache redisCache;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">refreshToken</span><span class="params">(LoginUser loginUser)</span> </span>&#123;</span><br><span class="line">    loginUser.setLoginTime(System.currentTimeMillis());</span><br><span class="line">    loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);</span><br><span class="line">    <span class="comment">// 根据 uuid 将 loginUser 缓存</span></span><br><span class="line">    String userKey = getTokenKey(loginUser.getToken());</span><br><span class="line">    redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">getTokenKey</span><span class="params">(String uuid)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Constants.LOGIN_TOKEN_KEY + uuid;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>缓存的 Redis Key 的<strong>统一前缀</strong>为 <code>&quot;login_tokens:&quot;</code> ，使用 Login 的用户唯一标识(<code>LoginUser.token</code>)作为<strong>后缀</strong>。</li></ul></li><li><p><code>&lt;4&gt;</code> 处，调用 <code>#createToken(Map claims)</code> 方法，生成 JWT 的 Token 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// application.yaml</span></span><br><span class="line"># token配置</span><br><span class="line">token:</span><br><span class="line">    # 令牌秘钥</span><br><span class="line">    secret: abcdefghijklmnopqrstuvwxyz</span><br><span class="line">    </span><br><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"><span class="comment">// 令牌秘钥</span></span><br><span class="line"><span class="meta">@Value</span>(<span class="string">"$&#123;token.secret&#125;"</span>)</span><br><span class="line"><span class="keyword">private</span> String secret;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">createToken</span><span class="params">(Map&lt;String, Object&gt; claims)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">            .setClaims(claims)</span><br><span class="line">            .signWith(SignatureAlgorithm.HS512, secret).compact();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>这里，我们采用了 <a href="https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt" target="_blank" rel="noopener"><code>jjwt</code></a> 库。</li><li>对 JWT 不了解的胖友，可以阅读下<a href="http://www.iocoder.cn/Fight/understanding-jwt/?self" target="_blank" rel="noopener">《JSON Web Token - 在Web应用间安全地传递信息》</a>和<a href="http://www.iocoder.cn/Fight/user-authentication-with-jwt/?self" target="_blank" rel="noopener">《八幅漫画理解使用 JSON Web Token 设计单点登录系统》</a>文章。</li><li>注意，不要把这里生成的 JWT 的 Token ，和我们上面的 <code>LoginUser.token</code>混淆在一起。<ul><li>因为 <code>LoginUser.token</code> 添加到 <code>claims</code> 中，最终生成了 JWT 的 Token 。所以，后续我们可以通过解码该 JWT 的 Token ，从而获得 <code>claims</code> ，最终获得到对应的 <code>LoginUser.token</code> 。</li><li>在 JWT 的 Token 中，使用 <code>LoginUser.token</code> 而不是 <code>userId</code> 的好处，可以带来更好的安全性，避免万一 <code>secret</code> 秘钥泄露之后，黑客可以顺序生成 <code>userId</code> 从而生成对应的 JWT 的 Token ，后续直接可以操作该用户的数据。不过，这么做之后就不是<strong>纯粹</strong>的 JWT ，解析出来的 <code>LoginUser.token</code> 需要查询对应的 LoginUser 的 Redis 缓存。详细的，我们在<a href="#anchor-7.4">「7.4 JwtAuthenticationTokenFilter」</a>会看到这个过程。</li></ul></li></ul></li></ul><p>至此，我们完成了基于用户名与密码的登陆认证的整个过程。虽然整个过程的代码有小几百行，不过逻辑还是比较清晰明了的。如果不太理解的胖友，可以在反复看两遍。</p><h2 id="7-4-JwtAuthenticationTokenFilter"><a href="#7-4-JwtAuthenticationTokenFilter" class="headerlink" title="7.4. JwtAuthenticationTokenFilter"></a><span id="anchor-7.4">7.4. JwtAuthenticationTokenFilter</span></h2><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/filter/JwtAuthenticationTokenFilter.java" target="_blank" rel="noopener">JwtAuthenticationTokenFilter</a> 中，继承 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java" target="_blank" rel="noopener">OncePerRequestFilter</a> 过滤器，实现了基于 Token 的认证。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JwtAuthenticationTokenFilter.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JwtAuthenticationTokenFilter</span> <span class="keyword">extends</span> <span class="title">OncePerRequestFilter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenService tokenService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doFilterInternal</span><span class="params">(HttpServletRequest request, HttpServletResponse response, FilterChain chain)</span></span></span><br><span class="line"><span class="function">            <span class="keyword">throws</span> ServletException, IOException </span>&#123;</span><br><span class="line">        <span class="comment">// &lt;1&gt; 获得当前 LoginUser</span></span><br><span class="line">        LoginUser loginUser = tokenService.getLoginUser(request);</span><br><span class="line">        <span class="comment">// 如果存在 LoginUser ，并且未认证过</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotNull(loginUser) &amp;&amp; StringUtils.isNull(SecurityUtils.getAuthentication())) &#123;</span><br><span class="line">            <span class="comment">// &lt;2&gt; 校验 Token 有效性</span></span><br><span class="line">            tokenService.verifyToken(loginUser);</span><br><span class="line">            <span class="comment">// &lt;3&gt; 创建 UsernamePasswordAuthenticationToken 对象，设置到 SecurityContextHolder 中</span></span><br><span class="line">            UsernamePasswordAuthenticationToken authenticationToken = <span class="keyword">new</span> UsernamePasswordAuthenticationToken(loginUser, <span class="keyword">null</span>, loginUser.getAuthorities());</span><br><span class="line">            authenticationToken.setDetails(<span class="keyword">new</span> WebAuthenticationDetailsSource().buildDetails(request));</span><br><span class="line">            SecurityContextHolder.getContext().setAuthentication(authenticationToken);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// &lt;4&gt; 继续过滤器</span></span><br><span class="line">        chain.doFilter(request, response);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，调用 TokenService 的 <code>#getLoginUser(request)</code> 方法，获得当前 LoginUser 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// application.yaml</span></span><br><span class="line"># token配置</span><br><span class="line">token:</span><br><span class="line">    # 令牌自定义标识</span><br><span class="line">    header: Authorization</span><br><span class="line"></span><br><span class="line"><span class="comment">// TokenService.jav</span></span><br><span class="line"><span class="comment">// 令牌自定义标识</span></span><br><span class="line"><span class="meta">@Value</span>(<span class="string">"$&#123;token.header&#125;"</span>)</span><br><span class="line"><span class="keyword">private</span> String header;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取用户身份信息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> LoginUser <span class="title">getLoginUser</span><span class="params">(HttpServletRequest request)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1.1&gt; 获取请求携带的令牌</span></span><br><span class="line">    String token = getToken(request);</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNotEmpty(token)) &#123;</span><br><span class="line">        <span class="comment">// &lt;1.2&gt; 解析 JWT 的 Token</span></span><br><span class="line">        Claims claims = parseToken(token);</span><br><span class="line">        <span class="comment">// &lt;1.3&gt; 从 Redis 缓存中，获得对应的 LoginUser</span></span><br><span class="line">        String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);</span><br><span class="line">        String userKey = getTokenKey(uuid);</span><br><span class="line">        <span class="keyword">return</span> redisCache.getCacheObject(userKey);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">getToken</span><span class="params">(HttpServletRequest request)</span> </span>&#123;</span><br><span class="line">    String token = request.getHeader(header);</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNotEmpty(token) &amp;&amp; token.startsWith(Constants.TOKEN_PREFIX)) &#123;</span><br><span class="line">        token = token.replace(Constants.TOKEN_PREFIX, <span class="string">""</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> token;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> Claims <span class="title">parseToken</span><span class="params">(String token)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Jwts.parser()</span><br><span class="line">            .setSigningKey(secret)</span><br><span class="line">            .parseClaimsJws(token)</span><br><span class="line">            .getBody();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1.1&gt;</code> 处，调用 <code>#getToken(request)</code> 方法，从请求头 <code>&quot;Authorization&quot;</code> 中，获得身份认证的 Token 。</li><li><code>&lt;1.2&gt;</code> 处，调用 <code>#parseToken(token)</code> 方法，解析 JWT 的 Token ，获得 Claims 对象，从而获得用户唯一标识(<code>LoginUser.token</code>)。</li><li><code>&lt;1.3&gt;</code> 处，从 Redis 缓存中，获得对应的 LoginUser 。</li></ul></li><li><p><code>&lt;2&gt;</code> 处，调用 TokenService 的 <code>#verifyToken(LoginUser loginUser)</code> 方法，验证令牌有效期。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> MILLIS_SECOND = <span class="number">1000</span>;</span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> MILLIS_MINUTE = <span class="number">60</span> * MILLIS_SECOND;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Long MILLIS_MINUTE_TEN = <span class="number">20</span> * <span class="number">60</span> * <span class="number">1000L</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证令牌有效期，相差不足 20 分钟，自动刷新缓存</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> loginUser 用户</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">verifyToken</span><span class="params">(LoginUser loginUser)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> expireTime = loginUser.getExpireTime();</span><br><span class="line">    <span class="keyword">long</span> currentTime = System.currentTimeMillis();</span><br><span class="line">    <span class="comment">// 相差不足 20 分钟，自动刷新缓存</span></span><br><span class="line">    <span class="keyword">if</span> (expireTime - currentTime &lt;= MILLIS_MINUTE_TEN) &#123;</span><br><span class="line">        String token = loginUser.getToken();</span><br><span class="line">        loginUser.setToken(token);</span><br><span class="line">        refreshToken(loginUser);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>实际上，这个方法的目的不是验证 Token 的有效性，而是刷新对应的 LoginUser 的缓存的过期时间。</li><li>考虑到避免每次请求都去刷新缓存的过期时间，所以过期时间不足 20 分钟，才会去刷新。</li></ul></li><li><p><code>&lt;3&gt;</code> 处，<strong>手动</strong>创建 UsernamePasswordAuthenticationToken 对象，设置到 SecurityContextHolder 中。因为，我们已经通过 Token 来完成认证了。</p></li><li><p><code>&lt;4&gt;</code> 处，继续过滤器。</p></li></ul><p>严格来说，RuoYi-Vue 并不是采用的<strong>无状态</strong>的 JWT ，而是使用 JWT 的 Token 的生成方式。</p><h2 id="7-5-权限验证"><a href="#7-5-权限验证" class="headerlink" title="7.5. 权限验证"></a>7.5. 权限验证</h2><p>在<a href="#anchor-3">「3. 进阶使用」</a>中，我们看到可以通过 Spring Security 提供的 <code>@PreAuthorize</code> 注解，实现基于 Spring EL 表达式的执行结果为 <code>true</code> 时，可以访问，从而实现灵活的权限校验。</p><p>在 RuoYi-Vue 中，通过 <code>@PreAuthorize</code> 注解的特性，使用其 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/PermissionService.java" target="_blank" rel="noopener">PermissionService</a> 提供的权限验证的方法。使用示例如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysDictDataController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@PreAuthorize</span>(<span class="string">"@ss.hasPermi('system:dict:list')"</span>)</span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/list"</span>)</span><br></pre></td></tr></table></figure><ul><li>请求 <code>/system/dict/data/list</code> 接口，会调用 PermissionService 的 <code>#hasPermi(String permission)</code> 方法，校验用户是否有指定的权限。</li><li>为什么这里会有一个 <code>@ss</code> 呢？在 Spring EL 表达式中，调用指定 Bean 名字的方法时，使用 <code>@</code> + Bean 的名字。在 RuoYi-Vue 中，声明 PermissionService 的 Bean 名字为 <code>ss</code> 。</li></ul><h3 id="7-5-1-判断是否有权限"><a href="#7-5-1-判断是否有权限" class="headerlink" title="7.5.1. 判断是否有权限"></a>7.5.1. 判断是否有权限</h3><p>在 PermissionService 中，定义了 <code>#hasPermi(String permission)</code> 方法，判断当前用户是否<strong>有</strong>指定的权限。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 所有权限标识</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ALL_PERMISSION = <span class="string">"*:*:*"</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> TokenService tokenService;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证用户是否具备某权限</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> permission 权限字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否具备某权限</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasPermi</span><span class="params">(String permission)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 如果未设置需要的权限，强制不具备。</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(permission)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    <span class="comment">// 如果不存在，或者没有任何权限，说明权限验证不通过</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断是否包含权限</span></span><br><span class="line">    <span class="keyword">return</span> hasPermissions(loginUser.getPermissions(), permission);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 判断是否包含权限</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> permissions 权限列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> permission  权限字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否具备某权限</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">hasPermissions</span><span class="params">(Set&lt;String&gt; permissions, String permission)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>比较简单，看看代码注释，就能够明白。</li></ul><p>在 PermissionService 中，定义了 <code>#lacksPermi(String permission)</code> 方法，判断当前用户是否<strong>没有</strong>指定的权限。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证用户是否不具备某权限，与 hasPermi逻辑相反</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> permission 权限字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否不具备某权限</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">lacksPermi</span><span class="params">(String permission)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> !hasPermi(permission);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 PermissionService 中，定义了 <code>#hasAnyPermi(String permissions)</code> 方法，判断当前用户是否<strong>有</strong>指定的<strong>任一</strong>权限。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String PERMISSION_DELIMETER = <span class="string">","</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证用户是否具有以下任意一个权限</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否具有以下任意一个权限</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasAnyPermi</span><span class="params">(String permissions)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 如果未设置需要的权限，强制不具备。</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(permissions)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    <span class="comment">// 如果不存在，或者没有任何权限，说明权限验证不通过</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断是否包含指定的任一权限</span></span><br><span class="line">    Set&lt;String&gt; authorities = loginUser.getPermissions();</span><br><span class="line">    <span class="keyword">for</span> (String permission : permissions.split(PERMISSION_DELIMETER)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (permission != <span class="keyword">null</span> &amp;&amp; hasPermissions(authorities, permission)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-5-2-判断是否有角色"><a href="#7-5-2-判断是否有角色" class="headerlink" title="7.5.2. 判断是否有角色"></a>7.5.2. 判断是否有角色</h3><p>在 PermissionService 中，定义了 <code>#hasRole(String role)</code> 方法，判断当前用户是否<strong>有</strong>指定的角色。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 判断用户是否拥有某个角色</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> role 角色字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否具备某角色</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasRole</span><span class="params">(String role)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 如果未设置需要的角色，强制不具备。</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(role)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    <span class="comment">// 如果不存在，或者没有任何角色，说明权限验证不通过</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断是否包含指定角色</span></span><br><span class="line">    <span class="keyword">for</span> (SysRole sysRole : loginUser.getUser().getRoles()) &#123;</span><br><span class="line">        String roleKey = sysRole.getRoleKey();</span><br><span class="line">        <span class="keyword">if</span> (SUPER_ADMIN.contains(roleKey) <span class="comment">// 超级管理员的特殊处理</span></span><br><span class="line">                || roleKey.contains(StringUtils.trim(role))) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>比较简单，看看添加的代码注释，就能够明白。</li></ul><p>在 PermissionService 中，定义了 <code>#lacksRole(String role)</code> 方法，判断当前用户是否<strong>没有</strong>指定的角色。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证用户是否不具备某角色，与 isRole逻辑相反。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> role 角色名称</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否不具备某角色</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">lacksRole</span><span class="params">(String role)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> !hasRole(role);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 PermissionService 中，定义了 <code>#hasAnyRoles(String roles)</code> 方法，判断当前用户是否<strong>有</strong>指定的<strong>任一</strong>角色。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionService.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROLE_DELIMETER = <span class="string">","</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 验证用户是否具有以下任意一个角色</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户是否具有以下任意一个角色</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">hasAnyRoles</span><span class="params">(String roles)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 如果未设置需要的角色，强制不具备。</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(roles)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    <span class="comment">// 如果不存在，或者没有任何角色，说明权限验证不通过</span></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断是否包含指定的任一角色</span></span><br><span class="line">    <span class="keyword">for</span> (String role : roles.split(ROLE_DELIMETER)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (hasRole(role)) &#123; <span class="comment">// 这里实现有点问题，会循环调用 hasRole 方法，重复从 Redis 中读取当前 LoginUser</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="7-6-各种处理器"><a href="#7-6-各种处理器" class="headerlink" title="7.6. 各种处理器"></a><span id="anchor-7.6">7.6. 各种处理器</span></h2><p>在 Ruoyi-Vue 中，提供了各种处理器，处理各种情况，所以我们汇总在<a href="#anchor-7.6">「7.6 各种处理器」</a> 中，一起来瞅瞅。</p><h3 id="7-6-1-AuthenticationEntryPointImpl"><a href="#7-6-1-AuthenticationEntryPointImpl" class="headerlink" title="7.6.1. AuthenticationEntryPointImpl"></a><span id="anchor-7.6.1">7.6.1. AuthenticationEntryPointImpl</span></h3><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/AuthenticationEntryPointImpl.java" target="_blank" rel="noopener">AuthenticationEntryPointImpl</a> 中，实现 Spring Security <a href="https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/AuthenticationEntryPoint.java" target="_blank" rel="noopener">AuthenticationEntryPoint</a> 接口，处理认失败的 <a href="https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/AuthenticationException.java" target="_blank" rel="noopener">AuthenticationException</a> 异常。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AuthenticationEntryPointImpl.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 认证失败处理类 返回未授权</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthenticationEntryPointImpl</span> <span class="keyword">implements</span> <span class="title">AuthenticationEntryPoint</span>, <span class="title">Serializable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">8970718410437077606L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">commence</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 响应认证不通过</span></span><br><span class="line">        <span class="keyword">int</span> code = HttpStatus.UNAUTHORIZED;</span><br><span class="line">        String msg = StringUtils.format(<span class="string">"请求访问：&#123;&#125;，认证失败，无法访问系统资源"</span>, request.getRequestURI());</span><br><span class="line">        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>响应认证不通过的 JSON 字符串。</li></ul><h3 id="7-6-2-GlobalExceptionHandler"><a href="#7-6-2-GlobalExceptionHandler" class="headerlink" title="7.6.2. GlobalExceptionHandler"></a><span id="anchor-7.6.2">7.6.2. GlobalExceptionHandler</span></h3><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/GlobalExceptionHandler.java" target="_blank" rel="noopener">GlobalExceptionHandler</a> 中，定义了对 Spring Security 的异常处理。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GlobalExceptionHandler.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GlobalExceptionHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@ExceptionHandler</span>(AccessDeniedException<span class="class">.<span class="keyword">class</span>) // 没有访问权限。使用 @<span class="title">PreAuthorize</span> 校验权限不通过时，就会抛出 <span class="title">AccessDeniedException</span> 异常</span></span><br><span class="line"><span class="class">    <span class="title">public</span> <span class="title">AjaxResult</span> <span class="title">handleAuthorizationException</span>(<span class="title">AccessDeniedException</span> <span class="title">e</span>) </span>&#123;</span><br><span class="line">        log.error(e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(HttpStatus.FORBIDDEN, <span class="string">"没有权限，请联系管理员授权"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler</span>(AccountExpiredException<span class="class">.<span class="keyword">class</span>) // 账号已过期</span></span><br><span class="line"><span class="class">    <span class="title">public</span> <span class="title">AjaxResult</span> <span class="title">handleAccountExpiredException</span>(<span class="title">AccountExpiredException</span> <span class="title">e</span>) </span>&#123;</span><br><span class="line">        log.error(e.getMessage(), e);</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler</span>(UsernameNotFoundException<span class="class">.<span class="keyword">class</span>) // 用户名不存在</span></span><br><span class="line"><span class="class">    <span class="title">public</span> <span class="title">AjaxResult</span> <span class="title">handleUsernameNotFoundException</span>(<span class="title">UsernameNotFoundException</span> <span class="title">e</span>) </span>&#123;</span><br><span class="line">        log.error(e.getMessage(), e);</span><br><span class="line">        <span class="keyword">return</span> AjaxResult.error(e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ... 省略对其它的异常类的处理的方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>基于 Spring MVC 提供的 <code>@RestControllerAdvice</code> + <code>@ExceptionHandler</code> 注解，实现全局异常的处理。不了解的话，可以看看<a href="http://www.iocoder.cn/Spring-Boot/SpringMVC/?self" target="_blank" rel="noopener">《芋道 Spring Boot SpringMVC 入门》</a>的<a href="http://www.iocoder.cn/Spring-Boot/Spring-Security/#" target="_blank" rel="noopener">「5. 全局异常处理」</a>小节。</li></ul><h3 id="7-6-3-LogoutSuccessHandlerImpl"><a href="#7-6-3-LogoutSuccessHandlerImpl" class="headerlink" title="7.6.3. LogoutSuccessHandlerImpl"></a><span id="anchor-7.6.3">7.6.3. LogoutSuccessHandlerImpl</span></h3><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/handler/LogoutSuccessHandlerImpl.java" target="_blank" rel="noopener">LogoutSuccessHandlerImpl</a> 中，实现 Spring Security <a href="https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessHandler.java" target="_blank" rel="noopener">LogoutSuccessHandler</a> 接口，自定义退出的处理，主动删除 LoginUser 在 Redis 中的缓存。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LogoutSuccessHandlerImpl.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义退出处理类 返回成功</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LogoutSuccessHandlerImpl</span> <span class="keyword">implements</span> <span class="title">LogoutSuccessHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenService tokenService;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 退出处理</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onLogoutSuccess</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Authentication authentication)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// &lt;1&gt; 获得当前 LoginUser</span></span><br><span class="line">        LoginUser loginUser = tokenService.getLoginUser(request);</span><br><span class="line">        <span class="comment">// 如果有登陆的情况下</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotNull(loginUser)) &#123;</span><br><span class="line">            String userName = loginUser.getUsername();</span><br><span class="line">            <span class="comment">// &lt;2&gt; 删除用户缓存记录</span></span><br><span class="line">            tokenService.delLoginUser(loginUser.getToken());</span><br><span class="line">            <span class="comment">// &lt;3&gt; 记录用户退出日志</span></span><br><span class="line">            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, <span class="string">"退出成功"</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// &lt;4&gt; 响应退出成功</span></span><br><span class="line">        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, <span class="string">"退出成功"</span>)));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，调用 TokenService 的 <code>#getLoginUser(request)</code> 方法，获得当前 LoginUser 。</p></li><li><p><code>&lt;2&gt;</code> 处，调用 TokenService 的 <code>#delLoginUser(String token)</code> 方法，删除 LoginUser 的 Redis 缓存。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TokenService.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delLoginUser</span><span class="params">(String token)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isNotEmpty(token)) &#123;</span><br><span class="line">        String userKey = getTokenKey(token);</span><br><span class="line">        <span class="comment">// 删除缓存</span></span><br><span class="line">        redisCache.deleteObject(userKey);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><code>&lt;3&gt;</code> 处，记录相应的退出成功日志。</p></li><li><p><code>&lt;4&gt;</code> 处，响应退出成功的 JSON 字符串。</p></li></ul><h2 id="7-7-登陆日志"><a href="#7-7-登陆日志" class="headerlink" title="7.7. 登陆日志"></a><span id="anchor-7.7">7.7. 登陆日志</span></h2><p>登陆日志实体。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysLogininfor.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SysLogininfor</span> <span class="keyword">extends</span> <span class="title">BaseEntity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** ID */</span></span><br><span class="line">    <span class="keyword">private</span> Long infoId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 用户账号 */</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登录状态 0成功 1失败 */</span></span><br><span class="line">    <span class="keyword">private</span> String status;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登录IP地址 */</span></span><br><span class="line">    <span class="keyword">private</span> String ipaddr;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 登录地点 */</span></span><br><span class="line">    <span class="keyword">private</span> String loginLocation;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 浏览器类型 */</span></span><br><span class="line">    <span class="keyword">private</span> String browser;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 操作系统 */</span></span><br><span class="line">    <span class="keyword">private</span> String os;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 提示消息 */</span></span><br><span class="line">    <span class="keyword">private</span> String msg;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** 访问时间 */</span></span><br><span class="line">    <span class="keyword">private</span> Date loginTime;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>每个字段比较简单，胖友自己根据注释理解下即可。</li></ul><p>对应表的创建 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> sys_logininfor (</span><br><span class="line">  info_id        <span class="built_in">bigint</span>(<span class="number">20</span>)     <span class="keyword">not</span> <span class="literal">null</span> auto_increment   <span class="keyword">comment</span> <span class="string">'访问ID'</span>,</span><br><span class="line">  user_name      <span class="built_in">varchar</span>(<span class="number">50</span>)    <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'用户账号'</span>,</span><br><span class="line">  ipaddr         <span class="built_in">varchar</span>(<span class="number">50</span>)    <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'登录IP地址'</span>,</span><br><span class="line">  login_location <span class="built_in">varchar</span>(<span class="number">255</span>)   <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'登录地点'</span>,</span><br><span class="line">  browser        <span class="built_in">varchar</span>(<span class="number">50</span>)    <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'浏览器类型'</span>,</span><br><span class="line">  os             <span class="built_in">varchar</span>(<span class="number">50</span>)    <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'操作系统'</span>,</span><br><span class="line">  <span class="keyword">status</span>         <span class="built_in">char</span>(<span class="number">1</span>)        <span class="keyword">default</span> <span class="string">'0'</span>               <span class="keyword">comment</span> <span class="string">'登录状态（0成功 1失败）'</span>,</span><br><span class="line">  msg            <span class="built_in">varchar</span>(<span class="number">255</span>)   <span class="keyword">default</span> <span class="string">''</span>                <span class="keyword">comment</span> <span class="string">'提示消息'</span>,</span><br><span class="line">  login_time     datetime                                 <span class="keyword">comment</span> <span class="string">'访问时间'</span>,</span><br><span class="line">  primary <span class="keyword">key</span> (info_id)</span><br><span class="line">) <span class="keyword">engine</span>=<span class="keyword">innodb</span> auto_increment=<span class="number">100</span> <span class="keyword">comment</span> = <span class="string">'系统访问记录'</span>;</span><br></pre></td></tr></table></figure><p>在 RuoYi-Vue 中，记录 SysLogininfor 的过程如下：</p><ul><li>首先，<strong>手动</strong>调用 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/task/AsyncFactory.java#L27-L80" target="_blank" rel="noopener">AsyncFactory#recordLogininfor(username, status, message, args)</a> 方法，创建一个 Java <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TimerTask.java" target="_blank" rel="noopener">TimerTask</a> 任务。</li><li>然后调用 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/task/AsyncManager.java#L38-L46" target="_blank" rel="noopener">AsyncManager#execute(TimerTask task)</a> 方法，提交到定时任务的线程中，延迟 <code>OPERATE_DELAY_TIME = 10</code> 秒后，存储该记录到数据库中。</li></ul><p>这样的好处，是可以实现<strong>异步</strong>存储日志到数据库中，提升 API 接口的性能。不过实际上，Spring 提供了 <code>@Async</code> 注解，方便的实现异步操作。不了解的胖友，可以看看<a href="http://www.iocoder.cn/Spring-Boot/Async-Job/?self" target="_blank" rel="noopener">《芋道 Spring Boot 异步任务入门》</a>。</p><h2 id="7-8-获得用户信息-API-接口"><a href="#7-8-获得用户信息-API-接口" class="headerlink" title="7.8. 获得用户信息 API 接口"></a>7.8. 获得用户信息 API 接口</h2><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/SysLoginController.java" target="_blank" rel="noopener">SysLoginController</a> 中，定义了 <code>/getInfo</code> 接口，获取登陆的用户信息。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysLoginController.java</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取用户信息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 用户信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"getInfo"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> AjaxResult <span class="title">getInfo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// &lt;1&gt; 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    SysUser user = loginUser.getUser();</span><br><span class="line">    <span class="comment">// &lt;2&gt; 角色标识的集合</span></span><br><span class="line">    Set&lt;String&gt; roles = permissionService.getRolePermission(user);</span><br><span class="line">    <span class="comment">// &lt;3&gt; 权限集合</span></span><br><span class="line">    Set&lt;String&gt; permissions = permissionService.getMenuPermission(user);</span><br><span class="line">    <span class="comment">// &lt;4&gt; 返回结果</span></span><br><span class="line">    AjaxResult ajax = AjaxResult.success();</span><br><span class="line">    ajax.put(<span class="string">"user"</span>, user);</span><br><span class="line">    ajax.put(<span class="string">"roles"</span>, roles);</span><br><span class="line">    ajax.put(<span class="string">"permissions"</span>, permissions);</span><br><span class="line">    <span class="keyword">return</span> ajax;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，调用 TokenService 的 <code>#getLoginUser(request)</code> 方法，获得当前 LoginUser 。</p></li><li><p><code>&lt;2&gt;</code> 处，调用 PermissionService 的 <code>#getRolePermission(SysUser user)</code> 方法，获得 LoginUser 拥有的角色<strong>标识</strong>的集合。代码如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysPermissionService.java</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ISysRoleService roleService;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取角色数据权限</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> user 用户信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 角色权限信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;String&gt; <span class="title">getRolePermission</span><span class="params">(SysUser user)</span> </span>&#123;</span><br><span class="line">    Set&lt;String&gt; roles = <span class="keyword">new</span> HashSet&lt;String&gt;();</span><br><span class="line">    <span class="comment">// 管理员拥有所有权限</span></span><br><span class="line">    <span class="keyword">if</span> (user.isAdmin()) &#123; <span class="comment">// 如果是管理员，强制添加 admin 角色</span></span><br><span class="line">        roles.add(<span class="string">"admin"</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; <span class="comment">// 如果非管理员，进行查询</span></span><br><span class="line">        roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> roles;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SysRoleServiceImpl.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SysRoleMapper roleMapper;</span><br><span class="line">    </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 根据用户ID查询权限</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> userId 用户ID</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 权限列表</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;String&gt; <span class="title">selectRolePermissionByUserId</span><span class="params">(Long userId)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获得 userId 拥有的 SysRole 数组</span></span><br><span class="line">    List&lt;SysRole&gt; perms = roleMapper.selectRolePermissionByUserId(userId);</span><br><span class="line">    <span class="comment">// 遍历 SysRole 数组，生成角色标识数组</span></span><br><span class="line">    Set&lt;String&gt; permsSet = <span class="keyword">new</span> HashSet&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (SysRole perm : perms) &#123;</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotNull(perm)) &#123;</span><br><span class="line">            permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(<span class="string">","</span>)));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> permsSet;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SysRoleMapper.xml</span></span><br><span class="line">&lt;sql id=<span class="string">"selectRoleVo"</span>&gt;</span><br><span class="line">    select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope,</span><br><span class="line">        r.status, r.del_flag, r.create_time, r.remark </span><br><span class="line">    from sys_role r</span><br><span class="line">        left join sys_user_role ur on ur.role_id = r.role_id</span><br><span class="line">        left join sys_user u on u.user_id = ur.user_id</span><br><span class="line">        left join sys_dept d on u.dept_id = d.dept_id</span><br><span class="line">&lt;/sql&gt;</span><br><span class="line"></span><br><span class="line">&lt;select id=<span class="string">"selectRolePermissionByUserId"</span> parameterType=<span class="string">"Long"</span> resultMap=<span class="string">"SysRoleResult"</span>&gt;</span><br><span class="line">	&lt;include refid=<span class="string">"selectRoleVo"</span>/&gt;</span><br><span class="line">	WHERE r.del_flag = '0' and ur.user_id = #&#123;userId&#125;</span><br><span class="line">&lt;/select&gt;</span><br></pre></td></tr></table></figure><ul><li>通过查询 <code>sys_role</code> 表，同时连接 <code>sys_user_role</code>、<code>sys_user</code>、<code>sys_dept</code> 表，将 <code>userId</code> 对应的 SysRole 相关信息都一次性查询出来。</li><li>返回结果 SysRoleResult 的具体定义，点击 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/resources/mapper/SysRoleMapper.xml#L7-L20" target="_blank" rel="noopener">传送门</a> 查看，实际就是 SysRole 实体类。</li></ul></li><li><p><code>&lt;3&gt;</code> 处，调用 SysPermissionService 的 <code>#getMenuPermission(SysUser user)</code> 方法，获得用户的 SysRoleMenu 的权限<strong>标识</strong>字符串的集合。</p></li><li><p><code>&lt;4&gt;</code> 处，返回用户信息的 AjaxResult 结果。</p></li></ul><p>通过调用该 <code>/getInfo</code> 接口，前端就可以根据角色<strong>标识</strong>、又或者权限<strong>标识</strong>，实现对页面级别的<strong>按钮</strong>实现权限控制，进行有权限时显示，无权限时隐藏。</p><h2 id="7-9-获取路由信息"><a href="#7-9-获取路由信息" class="headerlink" title="7.9. 获取路由信息"></a>7.9. 获取路由信息</h2><p>在 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/SysLoginController.java" target="_blank" rel="noopener">SysLoginController</a> 中，定义了 <code>/getRouters</code> 接口，获取获取路由信息。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SysLoginController.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"getRouters"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> AjaxResult <span class="title">getRouters</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获得当前 LoginUser</span></span><br><span class="line">    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());</span><br><span class="line">    <span class="comment">// 获得用户的 SysMenu 数组</span></span><br><span class="line">    SysUser user = loginUser.getUser();</span><br><span class="line">    List&lt;SysMenu&gt; menus = menuService.selectMenuTreeByUserId(user.getUserId());</span><br><span class="line">    <span class="comment">// 构建路由 RouterVo 数组。可用于 Vue 构建管理后台的左边菜单</span></span><br><span class="line">    <span class="keyword">return</span> AjaxResult.success(menuService.buildMenus(menus));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>具体的代码，比较简单，胖友自己去阅读下，嘿嘿。</li></ul><p>通过调用该 <code>/getRouters</code> 接口，前端就可以构建管理后台的左边菜单。</p><h2 id="7-10-权限管理"><a href="#7-10-权限管理" class="headerlink" title="7.10. 权限管理"></a>7.10. 权限管理</h2><p>如下的 Controller ，提供了 RuoYi-Vue 的<strong>权限管理</strong>功能，比较简单，自己去瞅瞅即可。</p><ul><li>用户管理 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/SysUserController.java" target="_blank" rel="noopener">SysUserController</a> ：用户是系统操作者，该功能主要完成系统用户配置。</li><li>菜单管理 <a href="https://github.com/yuyong725/calm-ruoyi-security/blob/master/src/main/java/cn/javadog/calm/ruoyi/security/controller/SysMenuController.java" target="_blank" rel="noopener">SysMenuController</a> ：配置系统菜单，操作权限，按钮权限标识等。</li></ul><h2 id="7-11-小小的建议"><a href="#7-11-小小的建议" class="headerlink" title="7.11. 小小的建议"></a>7.11. 小小的建议</h2><p>至此，我们完成了对 RuoYi-Vue 权限相关功能的源码进行解读，希望对你有一定的帮助。如果你的项目中需要权限相关的功能，建议不要直接拷贝 RuoYi-Vue 的代码，而是按照自己的理解，像卡姆一样，抽丝剥茧的剥离出来，甚至可以<strong>一点点</strong>“重新”实现一遍。在这个过程中，我们会有更加深刻的理解，甚至会有自己的一些小创新。</p></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/" title="Spring Boot 安全框架 Spring Security 入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2019/12/29/labs-springboot2-security/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="prev"><a href="/blog/2019/12/30/gitee-github-sync-push/" title="git同时提交到gitee和GitHub"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a></li><li class="next"><a href="/blog/2019/12/29/labs-springboot2-SpringMVC/" title="Spring Boot SpringMVC 入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->